This episode unpacks middleware as functions that wrap handlers: how to write them, register them with r.Use, and compose them explicitly with middleware.Chain. You will also explore chi's built-in middleware like RequestID, Logger, Recoverer, Compress, and BasicAuth, including notes on the RedirectSlashes open redirect fix.

Middleware is the reason many people choose chi. With one simple pattern — a function that takes and returns an http.Handler — you can add authentication, logging, recovery, compression, and load limits without changing a single line of any handler.
Episode 6 explains how middleware works, how to write it, and which built-in middleware is ready to use. By the end of the episode, you can build a structured middleware chain and know exactly the order in which it executes.
Middleware is a function with the signature func(http.Handler) http.Handler. When a request arrives, the middleware is called first, then hands control to the next handler — forming onion-like layers.
func tambahHeader(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("X-Powered-By", "chi")
next.ServeHTTP(w, req)
})
}next.ServeHTTP(w, req) is the hand-off point: before that line you can process the incoming request, after it you can process the outgoing response. This middleware adds a header to every response.
func timer(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
start := time.Now()
next.ServeHTTP(w, req)
durasi := time.Since(start)
w.Header().Set("X-Durasi", durasi.String())
})
}Note that the code before next.ServeHTTP runs when the request arrives; the code after it runs once the handler finishes. This is the pattern used by logging and metrics.
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(tambahHeader)
r.Get("/", homeHandler)r.Use(middleware.Recoverer) registers middleware that runs for all routes in this router — registration order is execution order: RequestID, Logger, Recoverer, then tambahHeader.
To apply middleware to just one route or group, use r.With:
r.With(middleware.BasicAuth("app",
map[string]string{"admin": "s3cret"})).
Get("/admin", adminHandler)r.With(middleware.BasicAuth(...)).Get("/admin", handler) creates a temporary new router with additional middleware, without affecting other routes.
middleware.Chain composes middleware explicitly into a single http.Handler:
final := middleware.Chain(
middleware.RequestID,
middleware.Logger,
middleware.Recoverer,
)(finalHandler)
r.Mount("/v1", final)middleware.Chain(...)(finalHandler) returns a new handler that runs RequestID, Logger, and Recoverer before finalHandler. Useful when you want to build a standalone handler outside of a router.
The first middleware in Chain is the outermost layer: it runs earliest. So put RequestID and Logger on top, and Recoverer inside them, so panics are still caught after logging has run.
chi provides ready-to-use utilities:
middleware.RequestID — adds a request ID and stores it in the context.middleware.RealIP — takes the real IP address from proxy headers.middleware.Logger — simple request logging.middleware.Recoverer — catches panics and returns status 500.middleware.Compress — gzip/brotli compression for responses.middleware.Timeout — handler execution time limit.middleware.Throttle — limits the number of concurrent requests.middleware.BasicAuth — basic header authentication.middleware.RedirectSlashes — normalizes trailing slashes.go doc github.com/go-chi/chi/v5/middlewarego doc github.com/go-chi/chi/v5/middleware shows the complete documentation of all built-in middleware along with examples.
In early 2026, a security vulnerability involving RedirectSlashes was discovered: under certain conditions, the generated redirect could become an open redirect — sending users to another domain. The fix was released as advisory GO-2026-4316.
What you need to do:
go get github.com/go-chi/chi/v5@latest
go mod tidygo get github.com/go-chi/chi/v5@latest pulls the version that includes the fix. Always make sure you're using the latest release — your application's security follows the security of your dependencies.
next.ServeHTTP.Good middleware is middleware that can move between projects without changes.
Key takeaways:
func(http.Handler) http.Handler.r.Use applies to the whole router; r.With inline per route.middleware.Chain composes middleware into a single handler.In the next episode 7 we'll deal with output: responses, JSON, and static files — JSON helpers, HTML and redirects, streaming, serving static files with http.FileServer, file uploads, and custom response writers.