golangtutorialAll tutorials

Go HTTP Middleware: Build a Production Chain

Build composable Go HTTP middleware for logging, recovery, request IDs, authentication, and timeouts.

Web Apis · Lesson 4Saved in this browser. No account required.

HTTP middleware wraps a handler to run logic before or after the request. The standard shape is a function from http.Handler to http.Handler.

Write one middleware

type Middleware func(http.Handler) http.Handler

func requestID(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		id := cryptoRandID()
		w.Header().Set("X-Request-ID", id)
		ctx := context.WithValue(r.Context(), requestIDKey{}, id)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

Middleware should pass the original request or a deliberate copy to next. If it decides the request is invalid, it writes a response and returns without calling the next handler.

Compose a chain

func chain(handler http.Handler, middleware ...Middleware) http.Handler {
	for i := len(middleware) - 1; i >= 0; i-- {
		handler = middleware[i](handler)
	}
	return handler
}

handler := chain(mux, recoverPanic, requestID, accessLog)

The first middleware listed is the outermost wrapper. Ordering matters: recovery should normally surround anything that might panic; request IDs should exist before logging needs them; authentication should run before protected handlers.

Recover without hiding failures

func recoverPanic(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if value := recover(); value != nil {
				log.Printf("panic: %v\n%s", value, debug.Stack())
				http.Error(w, "internal server error", http.StatusInternalServerError)
			}
		}()
		next.ServeHTTP(w, r)
	})
}

Recovery keeps one request from terminating the process, but it must record enough information to debug the defect.

Response-writer wrappers need care

Logging status codes requires a wrapper around http.ResponseWriter. Preserve optional interfaces such as http.Flusher when streaming or WebSockets matter. A simplistic wrapper can silently break handlers.

Keep middleware focused, dependency-injected, and independently testable with httptest. Business rules belong in services or handlers, not a growing global chain.