Routing

Register handlers with method helpers, nest routes with groups, and add scoped or inline middleware.

Router is chi underneath, with method helpers, groups, and middleware. Handlers use plain net/http signatures and never import chi, so the router stays the only thing that knows about it.

Registering handlers

NewRouter builds an empty router. Register a handler with the method helper that matches its verb, or with Handle for anything else.

r := server.NewRouter()

r.Get("/health", healthHandler)
r.Post("/items", createItem)
r.Put("/items/{id}", replaceItem)
r.Delete("/items/{id}", deleteItem)
r.Patch("/items/{id}", updateItem)

// Handle takes an explicit method and an http.Handler for anything the
// helpers do not cover.
r.Handle("OPTIONS", "/items", preflight)

Patterns use chi's syntax, which mirrors the stdlib {name} placeholder for a single segment and adds a trailing /* for a catch-all. Read those values back with the params helpers.

Groups

Group mounts a set of routes under a shared prefix. Middleware added inside the group applies only to routes registered on it, so a group is how you scope auth or logging to one part of the tree.

r.Group("/api", func(api *server.Router) {
	api.Use(server.AuthMiddleware(extractClaims, logger)) // scoped to /api

	api.Get("/items/{id}", func(w http.ResponseWriter, req *http.Request) {
		server.WriteJSON(w, http.StatusOK, map[string]string{"id": server.Param(req, "id")})
	})
})

Groups nest, so you can build a deeper tree by calling Group again inside the callback.

Middleware

Use appends middleware to a router's scope. Every request that the scope dispatches flows through the chain, including 404s, 405s, and CORS preflights.

r.Use(server.SlogMiddleware(server.SlogConfig{}, logger)) // applies to every route

Warning

chi panics if Use is called after a route is registered on the same scope, so add a scope's middleware before its routes.

For a one-off, With returns a sub-router that carries extra inline middleware without mutating the parent.

r.With(rateLimit).Post("/expensive", expensiveHandler)

Registering a route table

When routes are described as data rather than call sites, collect them as a []Route and hand the slice to Register. It walks the table and registers each entry through the HandleRouter interface that *Router satisfies.

routes := []server.Route{
	{Method: "GET", Pattern: "/health", Handler: healthHandler},
	{Method: "POST", Pattern: "/items", Handler: createItem},
}
server.Register(r, routes)

Inspecting the routes

LogRoutes walks every registered route and logs its method and pattern, which is a quick way to confirm the tree came out the way you expected. Server.Start calls it for you on startup.