The service had run for six months without a hiccup. Then one morning: 2% of requests returning a 500 — no log, no trace, no panic surfaced. The kind of bug that never reproduces locally. Half a day later, the cause: a panic in a handler, caught by a recover middleware… placed inside the logging middleware. The recover swallowed the panic after the logger had already written its line — but before the request ID was set. The result: a ghost 500, invisible in the dashboards.
A Go middleware is ten trivial lines. A chain of middleware in production is where the real problems hide: ordering, panic recovery, timeouts, ResponseWriter wrapping, context propagation. Here's what 14 years of running APIs taught me about the middleware that holds up — and the kind that falls over.
A middleware is just a function that wraps another
No magic, no framework required. A Go middleware is a function that takes an http.Handler and returns another. The canonical pattern, the one everything else follows:
type Middleware func(http.Handler) http.Handler
