# Middleware

> Wrap a handler in another handler to add logging, auth or recovery to every request.

Concept · Web services · https://levelcode.org/concepts/middleware

**Middleware** is code that runs around every request: before the handler, after it, or both. In Go it's simply a function that takes an `http.Handler` and returns a new one that wraps it:

```go
func Logging(log *slog.Logger) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			start := time.Now()
			next.ServeHTTP(w, r)                       // call the wrapped handler
			log.Info("request", "method", r.Method, "path", r.URL.Path, "took", time.Since(start))
		})
	}
}
```

This works because `http.Handler` is a one-method [interface](https://levelcode.org/concepts/go-interfaces). A wrapped handler is still a handler, so wrappers stack:

```go
handler := Recover(Logging(log)(RequestID(Auth(mux))))
```

## The onion

Requests travel inward through the layers and responses travel back out:

```text
Recover → Logging → RequestID → Auth → handler
Recover ← Logging ← RequestID ← Auth ←
```

That's why **order matters**:

- **Recovery goes outermost** so it catches panics from every layer inside it.
- **Request ID and logging go before auth,** so rejected requests are still logged with an ID.
- **Auth goes before the handlers it protects.** It can stop the chain by writing a `401` and not calling `next`.
- **Timeouts and body-size limits** go near the outside, so they cover all the work.

## What belongs in middleware

Cross-cutting concerns that are the same for every route belong here: panic recovery, request IDs, structured access logs, metrics and tracing ([logs, metrics and traces](https://levelcode.org/concepts/logs-metrics-traces)), authentication, CORS headers ([same-origin policy](https://levelcode.org/concepts/same-origin-policy-cors)), compression, and per-client rate limits.

Business logic doesn't belong here. "Does this user own this order?" depends on the route and the data, so it belongs in the handler or service.

## Passing data inward

Middleware passes data to inner handlers through the request [context](https://levelcode.org/concepts/context-cancellation):

```go
type ctxKey struct{}
ctx := context.WithValue(r.Context(), ctxKey{}, user)
next.ServeHTTP(w, r.WithContext(ctx))
```

Use an unexported key type so no other package can collide with it. Provide a typed getter like `UserFrom(ctx)`. Keep context values for request-scoped data, not for optional parameters.

To read the status code after `next` runs, wrap the `ResponseWriter` in a small struct that records `WriteHeader`. An [API gateway](https://levelcode.org/concepts/api-gateway-vs-load-balancer) is the same idea moved out of the process and in front of many services.
