What is Middleware?
The layered functions that run before a request reaches its handler — auth checks, logging, parsing, and more.
2 min read
Almost every backend framework has some concept of middleware: code that runs in the pipeline between a request arriving and its final handler producing a response, able to inspect, modify, short-circuit, or log the request or response as it passes through.
The pipeline model
Think of middleware as a chain, each link wrapping the next:
Request → [Logging] → [Auth check] → [Body parsing] → [Handler] → Response
↓ ↓ ↓
logs entry may reject may reject
(401) (400 bad JSON)
Each middleware function typically does one of three things: pass the request along unchanged, modify it (attach a parsed user object, add a request ID) before passing it along, or stop the chain entirely and return a response early (an auth failure returning 401 without ever reaching the actual handler).
What middleware is commonly used for
- Authentication — verify a token/session and attach the resulting user to the request, or reject with
401. - Logging — record method, path, response time, and status for every request.
- Body parsing — turn a raw request body into a usable object (e.g. parsing JSON) before the handler sees it.
- CORS handling — decide which origins are allowed to call this API from a browser.
- Rate limiting — reject requests that exceed a limit before they reach expensive application logic (see the next lesson).
- Error handling — a final middleware that catches unhandled exceptions from anywhere in the chain and turns them into a clean
500response instead of leaking a stack trace.
A pseudocode example
function authMiddleware(request, next):
token = request.headers["Authorization"]
if not token or not isValid(token):
return Response(401, "Unauthorized")
request.user = decodeUser(token)
return next(request) // pass control to the next link in the chain
function loggingMiddleware(request, next):
start = now()
response = next(request)
log(request.method, request.path, response.status, now() - start)
return response
Frameworks differ in exact syntax, but this "receive request, optionally do something, call next, optionally do something with the result" shape is close to universal — Express, Django, ASP.NET, and most others all model it this way.
Why middleware order matters
Middleware runs in the order it's registered, and that order is a real design decision, not an implementation detail. Logging middleware usually goes first, so it can time and record every request including ones that get rejected downstream. Auth middleware needs to run before any handler that assumes request.user exists. Getting the order wrong — say, parsing the body after a handler already tried to read it — produces bugs that are confusing precisely because the code "looks right" in isolation.
Rate limiting is one of the most common jobs middleware handles, and it deserves its own closer look next.