Middleware wraps a handler, adding behavior before/after the request:
// The pattern: func(next http.Handler) http.Handler
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r) // call the next handler
slog.Info("request",
"method", r.Method,
"path", r.URL.Path,
"duration", time.Since(start),
)
})
}
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return // don't call next
}
if !strings.HasPrefix(token, "Bearer ") {
http.Error(w, "invalid auth format", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
Convert panics to 500 responses instead of crashing the server:
func recoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
slog.Error("panic recovered", "error", err, "path", r.URL.Path)
http.Error(w, "internal server error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
// Apply middleware in order: recovery → logging → auth → handler
handler := recoveryMiddleware(
loggingMiddleware(
authMiddleware(mux),
),
)
http.ListenAndServe(":8080", handler)