The Middleware Pattern
A pipeline of small functions around every request — auth, logging, parsing, all composable.
30 minDifficulty 2/5node · patternsAI-writtenWritten by a model on 16 August 2026 and not yet read by a person. Checked automatically: schema, the pedagogical rules the hand-written material is held to, and every diagram parsed for real.
Before this
Why this exists
Every route in an API needs to check authentication, parse the request body, and log the request — writing that logic inside each of forty route handlers means forty copies of the same three things, and forty places to fix a bug in any of them. Middleware runs that shared logic once, in a pipeline every request passes through before reaching its specific handler.
The mental model
Think of middleware as a series of checkpoints a request passes through, one after another, before reaching its final destination (the route handler). Each checkpoint can inspect or modify the request, respond directly and stop the request from going further, or wave it through to the next checkpoint — that decision is made by whether it calls `next()`.
How it works
A middleware function decides whether the request continues
`function logger(req, res, next) { console.log(req.method, req.url); next(); }` does its work and calls `next()` to pass control to whatever comes after it — if it DIDN'T call `next()` and also didn't send a response, the request would simply hang forever, with nothing ever completing it.
Middleware runs in the order it's registered, forming a pipeline
Registering `app.use(logger)` before `app.use(authenticate)` before the route handler means EVERY request logs first, then authenticates, then reaches its handler — in exactly that order. Reversing the order changes behaviour: authenticating before logging means unauthenticated requests never even get logged, which may or may not be what you want.
A middleware can short-circuit the pipeline by responding directly
An authentication middleware that finds no valid session calls `res.status(401).json({ error: 'unauthorized' })` and does NOT call `next()` — the request stops here, never reaching the route handler at all. This is the mechanism that makes middleware genuinely useful for gating access: the handler behind it can simply assume authentication already happened, because it structurally can't be reached otherwise.
Middleware can attach data to the request for later middleware and handlers to use
An authentication middleware that successfully identifies a user commonly attaches it as `req.user = user` before calling `next()` — every subsequent middleware and the eventual route handler can then read `req.user` directly, without re-doing the authentication lookup themselves.
The mechanism
A request enters the pipeline and passes through each registered middleware in order. Each middleware either calls next() to pass control forward, or responds directly and ends the pipeline there. Only if every middleware in the chain calls next() does the request finally reach its matched route handler, which then produces the actual response.
flowchart LR R[Request] --> M1[Logger middleware] M1 -->|next| M2[Auth middleware] M2 -->|next, if valid| H[Route handler] M2 -->|else: res.status 401| End[Response sent, pipeline stops] H --> Resp[Response]
What people get wrong
- Middleware and route handlers are fundamentally different kinds of functions.
- A route handler is really just the LAST middleware in the chain for that specific route — it has the same signature (req, res, next) and follows the same rules, it just conventionally doesn't call next() because it's expected to send the final response. Seeing them as the same underlying mechanism clarifies why middleware composes so naturally with routing — there's no separate system, just a convention about which one sends the actual response.
- Forgetting to call next() in a middleware just means that middleware's logic doesn't run.
- Forgetting next() (and not sending a response either) means the ENTIRE pipeline stops there — the request hangs indefinitely, since nothing ever tells the router to move on or complete the response. This is one of the most common middleware bugs, and its symptom (a request that hangs and eventually times out, with no error message anywhere) doesn't obviously point back to a missing next() call.
- Middleware order doesn't matter as long as all the necessary middleware is registered somewhere.
- Since middleware runs strictly in registration order, and each one can inspect what earlier middleware attached to the request, order determines both WHAT information is available at each step and whether earlier gating (like auth) has already happened by the time a later middleware runs. A logging middleware registered after an authentication middleware that short-circuits will never log rejected requests at all, which may hide exactly the requests you'd most want visibility into.
When not to use it
- The logic is specific to exactly one route and will never apply to any other.
- Put it directly in that route's handler rather than as separate middleware — middleware's value is in being SHARED across multiple routes; a single-use piece of logic gains nothing from the extra indirection.
- You need to handle an error thrown anywhere in the middleware chain or a route handler.
- A dedicated error-handling middleware (recognized by having four parameters: err, req, res, next), registered last — this is a distinct middleware signature specifically for catching errors that occur earlier in the pipeline.
Terms
- Middleware
- — A function that runs during the request-response cycle, with the ability to inspect/modify the request, respond directly, or pass control to the next function in the pipeline.
- next()
- — The function a middleware calls to pass control to the next middleware (or route handler) in the pipeline; not calling it (without responding) hangs the request.
- Short-circuit
- — A middleware responding directly and not calling next(), stopping the request from reaching any subsequent middleware or the route handler.
- Error-handling middleware
- — A middleware with a four-argument signature (err, req, res, next), specifically for catching and responding to errors thrown earlier in the pipeline.
In an interview
A request to an authenticated route seems to hang forever with no response and no error. What's a likely cause in the middleware chain?
- some middleware in the chain neither calls next() nor sends a response itself
- the pipeline has nowhere to go from there — nothing tells the router the request is done or should proceed
- check each middleware for a code path (like a missed else branch) that falls through without calling next() or responding
Can you recall it?
What determines whether a request continues to the next middleware, stops entirely, or hangs, and why is a hang the most dangerous outcome?