Responses
Content-negotiated HTML and JSON from the same handler.
HAMR's pkg/respond package gives you helpers that detect what the client wants — HTML for browsers and HTMX, JSON for API clients — and serve the right format. The same handler can do both.
HTML responses
import "github.com/FyrmForge/hamr/pkg/respond"
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))
}respond.HTML takes an Echo context, a status code, and a Templ component. It writes the right Content-Type header and renders the component to the response body.
JSON responses
func (h *handler) ApiShow(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.JSON(c, http.StatusOK, book)
}Error responses
respond.Error handles content negotiation for errors automatically. Browser clients get an HTML error page; HTMX clients get a fragment; API clients get a JSON error object.
if err != nil {
return respond.Error(c, err)
}
// For specific status codes:
return respond.Error(c, echo.NewHTTPError(http.StatusNotFound, "book not found"))Validation errors
respond.ValidationError renders form errors in a way both HTMX and JSON clients understand. For HTMX, it returns a 422 with field errors as out-of-band swaps. For JSON, it returns an error object with field-keyed messages.
if err := form.Validate(input); err != nil {
return respond.ValidationError(c, err, formPage(c, input))
}Redirects
Use respond.Redirect for HTMX-aware redirects. For HTMX requests it sets HX-Redirect; for normal requests it returns a 303 with a Location header.
func (h *handler) Logout(c echo.Context) error {
h.sessions.Destroy(c)
return respond.Redirect(c, "/login")
}