Middleware is the idea that makes Express flexible. A middleware is a function that runs between the arrival of a request and your route handler. It can look at the request, change it, stop it with a response, or pass it on to the next function by calling next(). Logging, reading JSON bodies, checking logins, handling cross-origin requests and limiting traffic are all middleware. If you understand this lesson, the rest of Express becomes easy.
The shape of a middleware
A middleware takes three arguments: req, res and next. It must do one of two things. Either it sends a response, which ends the chain, or it calls next(), which passes control on. If it does neither, the request hangs until the client gives up. You register middleware with app.use. Here is a small logger and a JSON body parser.
import express from 'express';
const app = express();
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
console.log(`${req.method} ${req.originalUrl} ${res.statusCode} ${Date.now() - start}ms`);
});
next();
});
app.use(express.json());
app.post('/api/echo', (req, res) => {
res.json({ youSent: req.body });
});
app.listen(3000, () => console.log('Server running'));
The first middleware records the start time, asks Node to run a function when the response is finished, and then calls next(). When the response finishes, it prints the method, the URL, the status code and the time taken. The second one, express.json(), is built into Express. It reads the request body, and if the content type is JSON, it parses it and puts the result in req.body. Then the route can use req.body. Try it.
curl -X POST http://localhost:3000/api/echo -H "Content-Type: application/json" -d '{"title":"Hi"}'
{"youSent":{"title":"Hi"}}
The server terminal prints one line for each request, such as POST /api/echo 200 9ms. The number of milliseconds will differ on your computer. If you use Windows PowerShell, quoting rules for curl are different. A simple way is to test with a tool such as the REST Client extension for VS Code, or to use curl.exe with the data in a file. The code works the same.
Order is everything
Middleware and routes run in the order you write them. The logger must be registered before the routes you want it to log. express.json() must come before any route that reads req.body. If you put a route first, its response ends the chain and the middleware below never runs. When something does not work, a good first check is the order of the app.use lines.
What express.json() does when things go wrong
- If the request has no JSON content type,
req.bodyisundefinedin Express 5, not an empty object. A line such asreq.body.titlethen throws an error. Always check the body before you use it, which is what the validation helper in lesson 10 does. - If the body is not valid JSON, the parser rejects the request with a 400 error. Without your own error handler, Express answers with an HTML page that contains a stack trace. Lesson 10 replaces that with a clean JSON message.
- By default, the parser accepts bodies up to 100 kilobytes. You can set a smaller limit with
express.json({ limit: '10kb' }), which is a good protection for an API that only receives small objects.
Middleware for some routes only
You do not have to apply middleware to everything. You can give a path to app.use('/api', mw), or put middleware as an argument before a handler. We will do this in lesson 13 to protect the task routes and leave the login route open.
function requireJson(req, res, next) {
if (!req.is('application/json')) {
return res.status(415).json({ error: 'Send JSON with Content-Type: application/json' });
}
next();
}
app.post('/api/tasks', requireJson, (req, res) => {
// runs only if requireJson called next()
});
Middleware that other people wrote
Many packages are middleware. You install them with npm and add them with app.use. Some you will use in this course: cors (lesson 14), helmet (security headers, lesson 14) and express-rate-limit (limit repeated requests, lesson 14). Express itself includes express.json(), express.urlencoded() for HTML form data, and express.static('public'), which serves files from a folder, such as a built React app or images.
Passing data along with the request
A middleware can attach information to req so that later functions can use it. For example, an authentication middleware can set req.user, and then every route that comes after can read req.user.id. That is exactly how the login check in lesson 13 will work. Choose clear names, and do not overwrite built-in properties of req.
A word about error middleware
There is a special kind of middleware with four parameters: (err, req, res, next). Express recognises it by the count and uses it for errors. It must be registered after the routes. You will build one in lesson 10.
Your task
- Add the logger and
express.json()to your server and the/api/echoroute. Send a JSON body with curl and check the log line. - Move the logger below the echo route and see why it stops logging that route.
- Send an invalid JSON body such as
{badand see the HTML error. Keep this in mind for lesson 10. - Write a middleware called
addRequestIdthat setsreq.idto a random value fromcrypto.randomUUID()and add it to the log line.
How to check it worked
- Each request prints one log line with method, URL, status and time.
/api/echoreturns the JSON you sent.- Moving the logger after the route proves to you that order matters.