GoNetworking

Go Middleware Patterns: Constructing Robust Handlers

TT
TopicTrick Team
Go Middleware Patterns: Constructing Robust Handlers

Go Middleware Patterns: Constructing Robust Handlers

When building a production web server, you'll quickly realize that you need the same logic applied to many different routes. You want to log every request, check if a user is authenticated, and recover from unexpected panics without crashing the whole server.

In Go, Middleware is achieved through a simple but powerful pattern: wrapping one http.Handler inside another.

What is Go Middleware?

Go middleware is a function that accepts an http.Handler and returns a new http.Handler, injecting cross-cutting logic — such as logging, authentication, or rate limiting — without modifying individual route handlers. This pattern keeps business logic clean and behaviour consistent across all endpoints.

The Onion Model

    The Middleware Signature

    A Go middleware is simply a function that takes an http.Handler as an argument and returns a new http.Handler.

    go

    Chaining Middleware

    To apply middleware, you "wrap" your main handler. This can become nested and difficult to read if you have many layers.

    go

    Practical Example: Authentication Middleware

    Middleware is the standard place to enforce security. It checks for a valid token or session before allowing the request to reach your business logic.

    go

    Common Middleware Use Cases

    LoggingLog every request method & URL

    Essential for debugging and monitoring the health of your production environment.

    Panic Recoveryrecover() inside defer

    Ensures that a single bad request cannot crash your entire web server process.

    CORSSet Access-Control-Allow-Origin

    Handles cross-origin resource sharing headers for browser-based frontend applications.

    Rate LimitingThrottle IPs

    Protects your server from automated bots or brute-force attacks by limiting request velocity.

    Task / FeatureStandard HandlerMiddleware Handler
    ResponsibilityActual business logic / data fetchingCross-cutting concerns (Security, Logging)
    ReusabilityLow (Specific to one resource)High (Can be applied to thousands of routes)
    Control FlowTerminal (Sends the final response)Delegatory (Decides whether to continue the chain)

    Building a Middleware Chain Helper

    Manually nesting middleware gets unwieldy once you have four or more layers. A simple Chain helper function solves this elegantly.

    go

    This pattern is used by popular routers such as Chi and Gorilla Mux internally. You get all the benefits without any external dependency.

    Passing Data Between Middleware with Context

    Middleware often needs to pass computed values — such as an authenticated user ID — downstream to handlers. Go's context.Context is the idiomatic way to do this.

    go

    This approach avoids global state and keeps each request's data fully isolated, which is critical for concurrent servers.

    Panic Recovery Middleware

    One of the most important middleware functions in any production Go server is panic recovery. Without it, a single nil-pointer dereference in a handler will crash the entire process.

    go

    Notice how this leverages defer and recover() — the same language features covered in our guide on Go panic, recover, and defer. Panic recovery middleware is best placed as the outermost wrapper in your chain so it catches panics from all inner middleware too.

    Rate Limiting Middleware

    Rate limiting protects your API from abuse. The golang.org/x/time/rate package provides a production-quality token bucket limiter.

    go

    For IP-level rate limiting in real deployments, maintain a sync.Map of per-IP limiters.

    Testing Middleware

    Because middleware is just a function, it is straightforward to test in isolation using Go's built-in httptest package. See our dedicated guide on Go testing and benchmarks for a full walkthrough, but here is a quick example:

    go

    Real-World Middleware Libraries

    While writing your own middleware teaches the fundamentals, several well-maintained libraries add production features out of the box:

    Understanding the middleware pattern also underpins Go security best practices, where security headers and input validation are applied globally via middleware rather than in each handler.

    Middleware and the REST API Pattern

    All the middleware techniques in this post are applied directly in our comprehensive Go REST API project guide, where we wire up logging, auth, and recovery middleware around CRUD endpoints. If you are also exploring the broader web server layer, review our Go web server with net/http tutorial first.

    Next Steps

    Now that you can layer logic onto your handlers, it's time to put everything we've learned together. In our next tutorial, we will execute a Full REST API Project, building a complete tiered application from the database layer to the secure route handlers.

    Common Go Middleware Mistakes

    1. Not calling next.ServeHTTP(w, r) Middleware that forgets to call the next handler silently swallows all requests past it. Every middleware must either call next.ServeHTTP(w, r) to continue the chain or deliberately short-circuit with a response (e.g. returning 401 for failed auth).

    2. Writing headers after calling next Response headers must be set before w.WriteHeader() or any body write. If middleware tries to set a header after next.ServeHTTP has already written the response, the header is silently ignored.

    3. Modifying the request after the handler runs r is passed by pointer — modifications to the request are visible to the handler. But middleware runs around the handler: code after next.ServeHTTP sees the original request, not any changes the handler may have made internally.

    4. Creating a new http.ResponseWriter without wrapping properly Custom response writers (to capture status codes or response bodies) must implement the full http.ResponseWriter interface including Header(), Write(), and WriteHeader(). Missing any method causes a panic. See the net/http package docs.

    5. Applying middleware in the wrong order Middleware executes in the order it wraps the handler. Logging middleware should wrap everything (outermost) so it sees all requests. Auth middleware should run before business logic but after logging. Think of middleware as onion layers — order matters.

    Frequently Asked Questions

    What is the standard signature for Go HTTP middleware?

    go

    This pattern composes cleanly with any router that accepts http.Handler.

    How do I pass values from middleware to handlers? Use context.WithValue to attach values to the request context in middleware, then read them with r.Context().Value(key) in the handler. Use a private key type to avoid collisions with other packages.

    Which router has the best middleware support in Go? chi and gorilla/mux both have excellent middleware support. chi's Use method builds a middleware stack cleanly. For stdlib-only solutions, http.Handler chaining works without any third-party dependency.