Middleware in Express is a function with the signature (req, res, next) that runs between the moment a request arrives and the moment a response is sent. Each middleware can read or change req and res, end the request by sending a response, or call next() to hand the request to the next function in line. app.use(fn) adds middleware for every request, and Express runs everything in the order you registered it.
The Console logs the three steps in order for every request, and the route reads req.startedAt, a property the logger added. Send GET /missing: the two middleware still log, because app.use() matches every path, and then Express answers 404 since no route matched.
Forgetting next()
A middleware that neither sends a response nor calls next() leaves the request hanging. The client waits until it times out, and nothing appears in the logs, which makes this a common middleware bug. Delete next(); from logger above and send GET / again: the request never gets an answer.
The rule is simple: every path through a middleware ends in exactly one of these:
next()to continue,next(err)to jump to the error handler (see Express error handling),- a response such as
res.json()orres.status(401).send().
Calling next() after sending a response is also a bug. The next handler tries to send a second response and Express throws ERR_HTTP_HEADERS_SENT. Write return res.status(401).json(...) so the function stops there.
An auth check that stops the request
Middleware that rejects a request sends the response itself and never calls next(). Pass it as an extra argument to a route to protect only that route:
The first request gets 401 with {"error":"Missing or wrong API key"}. Add the header x-api-key: secret123 in the request bar and send it again: now the answer is {"users":42,"caller":"reporting-app"}. /public never runs the check.
Where middleware runs
| You write | Runs for |
|---|---|
app.use(fn) | Every request, every method |
app.use('/api', fn) | Paths that start with /api |
app.get('/admin', fn, handler) | GET /admin only, before handler |
router.use(fn) | Every route of that router |
Order is part of the meaning. A logger registered after the routes never logs requests those routes answered, because a route that sends a response does not call next(). Register app-wide middleware such as logging, body parsing and CORS first, then the routes, then the 404 and error handlers.
Built-in middleware
Express ships a few middleware functions you add with app.use(). The body parsers fill req.body; without them it is undefined.
| Middleware | What it does |
|---|---|
express.json() | Parses JSON bodies (Content-Type: application/json) |
express.urlencoded() | Parses HTML form posts (name=Ada&age=36) |
express.text() | Puts a plain text body in req.body as a string |
express.static('public') | Serves files from a folder |
Send POST /echo with the body {"name": "Ada"} and the answer includes "body":{"name":"Ada"}. Remove the express.json() line, send it again, and body is missing from the answer: req.body is undefined, and JSON.stringify leaves undefined values out. Packages such as cors, helmet and morgan are middleware too: you npm install them and pass them to app.use() the same way. The express.static page covers serving files.
Common mistakes
Calling the middleware instead of passing it. app.use(logger()) runs logger once at startup and registers its return value. Write app.use(logger). (express.json() has parentheses because it returns a middleware function.)
Adding the body parser after the routes. The routes run first and see req.body as undefined.
Expecting app.use('/admin', fn) to match one path. It also runs for /admin/users and every other path below /admin, for every method. To guard a single route, pass the middleware to app.get('/admin', fn, handler).
Frequently Asked Questions
What is the difference between middleware and a route handler?
Technically nothing: both are functions of (req, res, next). By convention a route handler ends the request with a response, while middleware usually does a shared job and calls next().
Can middleware be async?
Yes. In Express 5 an async middleware that throws or returns a rejected promise sends the error to your error handler automatically. Await what you need, then call next().
What does next('route') do?
It skips the remaining handlers of the current route and moves to the next route that matches. It works only in handlers added with app.get(), app.post() and the other method functions, not in app.use().
How do I skip a middleware for one route?
Register that route before the middleware, so it answers first, or check the path inside the middleware (if (req.path === '/health') return next();). Applying the middleware per router or per route instead of app-wide often removes the need.