Middleware Basics
Fast recall
Quick revision
Core answer
Whether you understand Middleware Basics beyond memorized syntax.
Giving a definition without explaining a practical trade-off.
Connect the concept to a small production example and state its complexity or failure mode.
Interview-ready guide
Interview answer
The pattern, executed:
func withHeader(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Custom", "yes") // BEFORE — pre-processing
next(w, r) // the actual handler
// AFTER — post-processing (rare — response already sent)
}
}
// wrap the handler
mux.HandleFunc("/mw", withHeader(myHandler))
// GET /mw → handler sees X-Custom: yes — verifiedThe shape: a function that RETURNS a function (the wrapper). The wrapper runs before the handler (pre: logging, auth, headers, request ID) and optionally after (post: response metrics — the response is already sent, so post is mostly cleanup).
The standard middleware chain — composable:
func chain(h http.HandlerFunc, mw ...func(http.HandlerFunc) http.HandlerFunc) http.HandlerFunc {
for i := len(mw) - 1; i >= 0; i-- { // wrap in reverse
h = mw[i](h)
}
return h
}
h := chain(handler, logging, auth, headers)The common middlewares every service has: logging (method, path, status, duration), auth (check token, reject or attach to context), panic recovery (defer recover → 500), request ID (generate + set in context and response header).
The http.Handler interface variant: middleware can also be func(http.Handler) http.Handler — the more general form (works on muxes, not just handlers). The two forms compose; pick one per codebase.
Middleware Basics
Direct Answer
A function that takes an http.HandlerFunc and returns one — wrapping the call with pre/post logic: func(next http.HandlerFunc) http.HandlerFunc. Chain them for logging, auth, headers, recovery.
Deep Dive & Architecture
The execution order, precisely: middleware[0] wraps middleware[1] wraps ... wraps the handler. Request flows OUT-IN (0→1→...→handler), response flows IN-OUT. The wrap-in-reverse in a chain function exists because each mw takes the NEXT thing to call — building from the innermost outward means iterating the slice backward.
The pre/post asymmetry: BEFORE next(), you own the request — modify it (add context values, change r), write headers (w.Header().Set before any Write), even short-circuit (return without calling next — auth reject, rate-limit). AFTER next(), the response is committed — you can't change headers/status (already sent); you CAN observe (log duration, count status from a recording wrapper) and clean up.
The short-circuit pattern, which is middleware's most important behavior: auth middleware checks the token; invalid → WriteHeader(401) and RETURN (no next call). The handler never runs. This is how middleware does enforcement — the wrapping makes it apply to every handler in the chain, consistently.
The panic-recovery middleware — the one every service needs: the deferred func inside the wrapper catches the panic, writes a 500, logs the stack — the goroutine-per-request model means an unhandled panic would crash the whole server without the middleware boundary (net/http actually recerves handler panics per-connection since 1.6, but a middleware recovery gives you logging and clean 500s).
Common Mistakes & Pitfalls
Giving a definition without explaining a practical trade-off.
Check your understanding
Answer these without looking back at the article.
- When would you use Middleware Basics in a real project?
- How would you compare Middleware Basics with an alternative?
- What is a common mistake when using Middleware Basics?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.