What is middleware and why is it used in web applications?
Updated Feb 20, 2026
Short answer
Middleware is code that sits in the request/response pipeline between the server receiving a request and your route handler producing a response. Each piece of middleware can inspect or modify the request, short-circuit it, or pass it along to the next one. It exists so that cross-cutting concerns — authentication, logging, compression, CORS, error handling — live in one composable place instead of being repeated in every handler.
Deep explanation
A web framework's request pipeline is a chain. A request enters, passes through an ordered list of middleware functions, reaches the handler, and the response travels back out through the same chain in reverse.
The canonical signature makes the chaining explicit:
function requestLogger(req, res, next) { const start = Date.now(); res.on('finish', () => { console.log(`${req.method} ${req.url} ${res.statusCode} ${Date.now() - start}ms`); }); next(); // hand control to the next middleware}
app.use(requestLogger);Three properties matter:
- Order is behaviour. Middleware runs in registration order. Body parsing must precede anything that reads
req.body; authentication must precede authorisation. Registering a rate limiter after your routes means it never protects them. - Any link can terminate the chain. Middleware that does not call
next()and instead sends a response ends the request. This is how auth guards reject unauthenticated traffic before a handler ever runs. - It wraps both directions. Because the response unwinds back through the chain, middleware can also post-process — set headers, compress the body, record metrics.
The trade-off is that a pipeline is global state applied implicitly. A request failing for a non-obvious reason is often a middleware ordering problem, and every request pays the cost of every middleware registered ahead of it — so expensive work belongs on specific routes rather than app.use.
Real-world example
A JSON API needs every /api/admin/* route to require an admin session. Without middleware, every handler repeats the same six lines. With it, the check is declared once:
async function requireAdmin(req, res, next) { const session = await getSession(req); if (!session) return res.status(401).json({ error: 'Not signed in' }); if (session.role !== 'admin') return res.status(403).json({ error: 'Forbidden' }); req.user = session.user; // hand the resolved user to downstream handlers next();}
app.use('/api/admin', requireAdmin);Every admin route is now protected, and a new route added next month is protected automatically — the security property is structural rather than something a developer has to remember.
Common mistakes
- - Registering middleware after the routes it is meant to affect, so it silently never runs.
- - Forgetting to call `next()`, which leaves the request hanging until it times out.
- - Calling `next()` *and* sending a response, causing a 'headers already sent' error.
- - Putting expensive work (a DB lookup, a remote call) in global middleware, so every request pays for it — including static assets and health checks.
- - Registering an error handler in the wrong position
- most frameworks require it last, and with a distinct arity such as `(err, req, res, next)`.
- - Mutating the request object with unnamespaced properties, causing collisions between unrelated middleware.
Follow-up questions
- How does error-handling middleware differ from ordinary middleware?
- What is the difference between application-level and route-level middleware?
- How would you short-circuit a request from middleware?
- Does middleware exist outside web frameworks?