Routing
Define your routes with Echo and HAMR's middleware groups.
HAMR uses Echo v4 under the hood. Routes are registered in internal/web/server.go via a RegisterRoutes function that receives all your dependencies.
Defining a route
func RegisterRoutes(srv *server.Server, deps *Deps) {
e := srv.Echo()
homeHandler := home.NewHandler()
e.GET("/", homeHandler.Index)
booksHandler := books.NewHandler(deps.BookRepo)
e.GET("/books", booksHandler.List)
e.GET("/books/:id", booksHandler.Show)
e.POST("/books", booksHandler.Create)
}Route groups
HAMR organizes routes into groups so middleware is applied consistently. A typical setup has three groups:
// Health check — no session middleware.
e.GET("/health", healthHandler.Health)
// Site routes — sessions, CSRF, flash, security headers.
site := e.Group("")
site.Use(middleware.Logging())
site.Use(hamrmw.SecureWithConfig(hamrmw.SecureConfig{
ContentSecurityPolicy: csp,
}))
site.Use(hamrmw.FlashWithConfig(hamrmw.FlashConfig{Secure: !deps.DevMode}))
site.Use(hamrmw.CSRFWithConfig(hamrmw.CSRFConfig{Secure: !deps.DevMode}))
site.Use(hamrmw.OptionalAuth(authCfg))
site.GET("/", homeHandler.Index)
site.GET("/books", booksHandler.List)Auth and RBAC are per-route
Authentication middleware is applied to individual routes, not to the whole group. This way, looking at a route line tells you exactly what access it requires.
requireAuth := hamrmw.RequireAuth(authCfg)
requireAdmin := hamrmw.RequireRoles(roleChecker, "admin")
// Public routes — no auth.
site.GET("/", homeHandler.Index)
site.GET("/books", booksHandler.List)
// Authenticated routes.
site.GET("/dashboard", dashboardHandler.Index, requireAuth)
site.POST("/books", booksHandler.Create, requireAuth)
// Admin routes.
site.GET("/admin", adminHandler.Index, requireAuth, requireAdmin)API routes
JSON API routes typically live in their own group with bearer-token auth and CORS instead of session cookies and CSRF.
func RegisterRoutes(srv *server.Server, deps *Deps) {
e := srv.Echo()
api := e.Group("/api")
api.Use(hamrmw.CORSWithConfig(corsConfig))
api.Use(hamrmw.RateLimit(deps.RateLimitStore))
api.GET("/health", healthHandler.Health)
api.GET("/books", booksHandler.List, hamrmw.Auth(authCfg))
}Static pages
Pages that don't depend on database or session state can be registered as static pages. The dev server pre-generates them as HTML files at build time, and the runtime server still serves them as live routes.
func RegisterStaticPages(srv *server.Server) {
aboutHandler := about.NewHandler()
srv.StaticPage("/about", aboutHandler.About)
docsHandler := docs.NewHandler()
srv.StaticPage("/docs", docsHandler.Overview)
srv.StaticPage("/docs/routing", docsHandler.Routing)
}