Compose request processing with middleware
Middleware is a function in Express's request pipeline. It can inspect a request, attach trusted server-generated context, finish a response, or pass control to the next handler. Registration order is execution order. This makes the position of a JSON parser, authentication check and error handler part of the application's behavior.
Trace an ordered pipeline
Run this as middleware.mjs after installing express@5. The request ID is generated on the server rather than copied from an arbitrary client header.
import express from 'express';
import { randomUUID } from 'node:crypto';
const app = express();
app.use((req, res, next) => {
req.requestId = randomUUID();
res.setHeader('X-Request-Id', req.requestId);
const started = Date.now();
res.on('finish', () => {
console.log(req.requestId, req.method, res.statusCode, Date.now() - started);
});
next();
});
app.use(express.json({ limit: '16kb' }));
function requireJson(req, res, next) {
if (!req.is('application/json')) {
return res.status(415).json({ error: 'Use application/json' });
}
next();
}
app.post('/api/notes', requireJson, (req, res) => {
const text = req.body?.text;
if (typeof text !== 'string' || !text.trim() || text.length > 500) {
return res.status(400).json({ error: 'Provide text of 1–500 characters' });
}
res.status(201).json({ data: { text: text.trim() }, requestId: req.requestId });
});
app.use((req, res) => res.status(404).json({ error: 'Route not found' }));
app.use((err, req, res, next) => {
if (res.headersSent) return next(err);
const status = err.status === 413 ? 413 : err.type === 'entity.parse.failed' ? 400 : 500;
res.status(status).json({ error: status === 500 ? 'Internal server error' : 'Invalid request body', requestId: req.requestId });
});
app.listen(3000, '127.0.0.1');
A valid POST gets a request ID, passes through JSON parsing and the route-specific Content-Type check, then returns 201. The finish event logs the completed status. Wrong media type returns 415. Invalid fields return 400. Malformed JSON skips normal routing and reaches the error handler.
Use next deliberately
next() continues normal processing; next(error) selects error handling. A function that neither responds nor calls next leaves a request waiting. Return after a terminal response. Error middleware needs all four arguments, even if a parameter is unused, and belongs after routes. In Express 5, errors from returned async handlers are forwarded, but a detached callback must forward its own error.
Security context belongs on the server
Authentication middleware should verify credentials and set a trusted identity. Authorization checks whether that identity may perform a particular operation. A client-supplied role field is not proof of permission. CORS controls browser access behavior and is not authentication. Configure trusted proxy behavior only for your actual hosting topology.
Practice checkpoint
Add timing middleware only to /api routes using app.use('/api', ...). Compare a normal response, a malformed JSON request and an unknown path. Log request IDs and statuses, but do not log authorization headers, passwords or complete student submissions. Explain which pipeline stage each request reaches.