Middleware Patterns
Middleware: Cross-Cutting Control Layer
Middleware in Go wraps HTTP handlers to implement cross-cutting concerns such as authentication, logging, tracing, rate limiting, and error handling. In production systems, middleware order and composition directly affect reliability and security.
Middleware governs:
- Authentication enforcement
- Request tracing
- Error formatting
- Rate limiting
- Logging consistency
- Latency measurement
Real Production Failure: Authentication Bypass
A service mounted authentication middleware after routing logic. Some endpoints were registered before the middleware chain was applied. Under certain paths, requests bypassed authentication entirely.
The issue existed for weeks before discovery.
Root cause: misunderstanding middleware chaining order.
Middleware Mechanics
Middleware wraps an http.Handler and returns a new handler.
func Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("incoming request")
next.ServeHTTP(w, r)
})
}
Order matters. If middleware A wraps B, A executes first.
Execution Order Example
r.Use(RequestID) r.Use(Logger) r.Use(Recoverer)
Execution flow:
- RequestID
- Logger
- Recoverer
- Handler
- Recoverer exit
- Logger exit
- RequestID exit
Context Enrichment Pattern
Middleware commonly enriches request context.
type ctxKey string
const userKey ctxKey = "user"
func Auth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user := authenticate(r)
ctx := context.WithValue(r.Context(), userKey, user)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
Keep context values small and request-scoped.
Error Handling Middleware
Centralized error formatting ensures consistent responses.
func ErrorHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rec := recover(); rec != nil {
http.Error(w, "internal error", 500)
}
}()
next.ServeHTTP(w, r)
})
}
Prefer structured logging inside recovery blocks.
Observability Injection
Latency Measurement
start := time.Now() next.ServeHTTP(w, r) duration := time.Since(start) metrics.Record(duration)
Request ID Propagation
Attach request ID early in middleware chain.
Tracing Integration
Open tracing spans in middleware, close after handler returns.
Authentication Layering
Authentication middleware should be close to router grouping:
r.Route("/api", func(r chi.Router) {
r.Use(AuthMiddleware)
r.Get("/profile", profileHandler)
})
Never rely on manual checks inside handlers only.
Idempotency Middleware
For critical endpoints (payments, order creation), use idempotency keys.
key := r.Header.Get("Idempotency-Key")
if key == "" {
http.Error(w, "missing key", 400)
return
}
Middleware can enforce idempotency validation.
Rate Limiting Middleware
func RateLimit(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !allow(r) {
http.Error(w, "too many requests", 429)
return
}
next.ServeHTTP(w, r)
})
}
Integrate with metrics for visibility.
Middleware Depth and Performance
Each middleware adds stack depth and execution overhead. Keep chain minimal and focused.
Avoid:
- Heavy computation inside middleware
- Business logic inside cross-cutting layer
- Redundant logging
Short-Circuit Behavior
Middleware may stop execution without calling next.
if !authorized {
http.Error(w, "forbidden", 403)
return
}
Ensure short-circuit logic is intentional and tested.
Testing Middleware
req := httptest.NewRequest("GET", "/", nil)
rr := httptest.NewRecorder()
handler := Auth(finalHandler)
handler.ServeHTTP(rr, req)
Test middleware in isolation.
Common Anti-Patterns
- Authentication after handler registration
- Business logic inside middleware
- Storing large objects in context
- Swallowing panics without logging
- Unbounded middleware stacking
Operational Checklist
- Middleware order documented
- Authentication consistently applied
- Error recovery enabled
- Request ID injected early
- Latency measured centrally
- No heavy logic in middleware
- Short-circuit paths tested
Final Perspective
Middleware is the nervous system of your HTTP layer. It coordinates cross-cutting behavior and enforces system-wide guarantees. In production systems, disciplined middleware design ensures consistency, observability, and security across all routes. Misuse leads to subtle, high-impact failures.