Basics

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

internal/web/handler/books/handler.go
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:

internal/web/server.go
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.

convention
// 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:

internal/web/handler/dashboard/handler.go
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))
}
Type-safe context
HAMR provides pkg/ctx with generic helpers if you want type-safe context keys instead of relying on type assertions. See the middleware docs for details.