Handlers
Methods on a struct that hold your dependencies and serve responses.
A HAMR handler is a struct that holds its dependencies (repos, services, config) plus methods that satisfy Echo's HandlerFunc signature. Handlers are constructed once at startup and registered against routes.
A basic handler
package books
import (
"net/http"
"github.com/FyrmForge/hamr/pkg/respond"
"github.com/labstack/echo/v4"
"github.com/yourname/myapp/internal/repo"
)
type handler struct {
repo repo.BookRepo
}
func NewHandler(repo repo.BookRepo) *handler {
return &handler{repo: repo}
}
// GET /books
func (h *handler) List(c echo.Context) error {
books, err := h.repo.All(c.Request().Context())
if err != nil {
return respond.Error(c, err)
}
return respond.HTML(c, http.StatusOK, listPage(c, books))
}
// GET /books/:id
func (h *handler) Show(c echo.Context) error {
book, err := h.repo.Get(c.Request().Context(), c.Param("id"))
if err != nil {
return respond.Error(c, err)
}
return respond.HTML(c, http.StatusOK, showPage(c, book))
}Dependency injection
There's no DI container in HAMR. Dependencies are passed explicitly via constructors. The RegisterRoutes function receives a Deps struct that holds everything your handlers need:
type Deps struct {
Store repo.Store
SessionManager *auth.SessionManager
AuthService *service.AuthService
FileStorage storage.FileStorage
Hub *websocket.Hub
}
func RegisterRoutes(srv *server.Server, deps *Deps) {
e := srv.Echo()
// Construct handlers with their specific dependencies.
booksHandler := books.NewHandler(deps.Store)
authHandler := auth.NewHandler(deps.AuthService, deps.SessionManager)
e.GET("/books", booksHandler.List)
e.POST("/login", authHandler.Login)
}Route comments
Document each handler method with the route that calls it. This convention makes it trivial to find the handler for a given URL with a single grep.
// GET /books
func (h *handler) List(c echo.Context) error { ... }
// POST /books
func (h *handler) Create(c echo.Context) error { ... }
// GET /books/:id/edit
func (h *handler) Edit(c echo.Context) error { ... }Getting the current user
When auth middleware (RequireAuth or OptionalAuth) runs, it loads the subject from your SubjectLoader callback into the request context. Read it back with middleware.GetSubject:
import "github.com/FyrmForge/hamr/pkg/middleware"
func (h *handler) Index(c echo.Context) error {
user, ok := middleware.GetSubject(c).(*repo.User)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized)
}
return respond.HTML(c, http.StatusOK, dashboardPage(c, user))
}pkg/ctx with generic helpers if you want type-safe context keys instead of relying on type assertions. See the middleware docs for details.