Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/go-chi/chi/llms.txt

Use this file to discover all available pages before exploring further.

Chi middleware is plain net/http middleware — every middleware is just a function with the signature func(http.Handler) http.Handler. There is nothing chi-specific about the type; any middleware compatible with the Go standard library will work in a chi router without any adapters or wrappers.

The middleware signature

Every chi middleware is a function that accepts the next handler in the chain and returns a new handler that wraps it. When a request arrives, the outermost handler runs first; each layer can inspect or modify the request before calling next.ServeHTTP, and optionally inspect or modify the response after it returns.
middleware.go
// Every chi middleware follows this exact signature.
func MyMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Work before the next handler (pre-processing)
        next.ServeHTTP(w, r)
        // Work after the next handler (post-processing)
    })
}

Applying middleware with r.Use()

r.Use() attaches one or more middlewares to the router’s stack. Every request that passes through that router will run all middlewares in the order they were registered — the first Use() call executes first, wrapping the outermost layer of the chain.
router_use.go
package main

import (
    "net/http"

    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
)

func main() {
    r := chi.NewRouter()

    // Execution order: RequestID → Logger → Recoverer → handler
    r.Use(middleware.RequestID)
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)

    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("hello"))
    })

    http.ListenAndServe(":3000", r)
}
Middleware registered with r.Use() applies to every route defined on that router and any sub-routers mounted beneath it. If you need to apply middleware to only a subset of routes, use r.With() or a scoped r.Group().

Applying middleware inline with r.With()

r.With() creates a temporary inline router that applies the listed middlewares only to the single endpoint it wraps. The base router’s middleware stack still executes for every request; the With() middlewares run as an additional inner layer.
router_with.go
package main

import (
    "net/http"

    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
)

func paginate(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // parse page/limit from query string …
        next.ServeHTTP(w, r)
    })
}

func main() {
    r := chi.NewRouter()
    r.Use(middleware.Logger)

    r.Route("/articles", func(r chi.Router) {
        // paginate applies only to these two GET routes
        r.With(paginate).Get("/", listArticles)
        r.With(paginate).Get("/{month}-{day}-{year}", listArticlesByDate)

        r.Post("/", createArticle) // no paginate middleware
    })

    http.ListenAndServe(":3000", r)
}

Middleware execution order

Chi builds a stack where the first Use() call wraps the chain at the outermost position. Given:
execution_order.go
r.Use(A)
r.Use(B)
r.Use(C)
The request flows through the chain as: A → B → C → handler → C → B → A. Each middleware has the opportunity to act both before the inner handlers run (on the request path) and after they complete (on the response path).
1

Request enters A

The first-registered middleware receives the request first. It can read or modify headers, the URL, or the context before passing control deeper.
2

A calls next — request enters B

B can further inspect or augment the request, optionally short-circuiting the chain by writing a response and returning without calling next.ServeHTTP.
3

B calls next — request enters C, then the handler

The innermost middleware C runs just before the route handler. After the handler writes its response, execution unwinds back through C, B, and A.

A minimal custom middleware

The example below shows the canonical pattern for a middleware that stores a value on the request context and makes it available to every downstream handler.
custom_middleware.go
package main

import (
    "context"
    "net/http"
)

// contextKey is an unexported type to avoid key collisions across packages.
type contextKey string

const userContextKey contextKey = "user"

// SetUserMiddleware is a middleware that sets a hypothetical user identifier
// on the request context and calls the next handler in the chain.
func SetUserMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Derive a new context carrying the "user" value
        ctx := context.WithValue(r.Context(), userContextKey, "123")

        // Pass the updated request with the new context to the next handler.
        // Any previously set context values remain accessible.
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// A handler that reads the value set by SetUserMiddleware above.
func MyHandler(w http.ResponseWriter, r *http.Request) {
    user, _ := r.Context().Value(userContextKey).(string)
    w.Write([]byte("hello, " + user))
}

The built-in middleware package

Chi ships an optional middleware sub-package that provides a suite of production-ready handlers. Import it alongside the router:
import.go
import (
    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
)
The package covers logging, panic recovery, request IDs, compression, throttling, authentication, and more. Because every handler in the package has the standard func(http.Handler) http.Handler signature, you can freely mix it with any other compatible community middleware.

Built-In Middleware

Full reference for every handler in the middleware package, grouped by category.

Client IP Extraction

Safely resolve the real client IP address behind proxies and CDNs.

Custom Middleware

Patterns for writing your own middleware — context values, auth guards, and response capture.

Build docs developers (and LLMs) love