Middleware is the most important idea in Express. A middleware is a function that sits between the incoming request and your route. It can look at the request, change it, send an answer by itself, or pass it on by calling next(). Think of a security desk at an office: every visitor passes the desk first, and the desk may let them through, write down their name, or turn them away.
Everything else in this back end is built from middleware: reading JSON, allowing the React app to call the API, logging, signing in, rate limiting, uploads and error handling. Once you understand one, you understand all of them.
The shape of a middleware
// Middleware = a function that runs before your route. It can look at the request, change it,
// answer it, or call next() to pass it along.
export function logger(req, res, next) {
const started = Date.now();
res.on('finish', () => {
console.log(`${req.method} ${req.originalUrl} -> ${res.statusCode} (${Date.now() - started} ms)`);
});
next();
}
It takes three arguments: req, res and next. It does its work and calls next(). If it forgot to call next() (and did not send an answer), the request would hang forever and the browser would spin. This logger uses an event from lesson 7: res.on('finish', ...) runs after the response has been sent, so it can report the final status code and the time taken.
Wiring the pipeline
// Lesson 10: middleware, JSON bodies and CORS.
import 'dotenv/config';
import express from 'express';
import cors from 'cors';
import { logger } from './middleware/logger.js';
const app = express();
const port = Number(process.env.PORT) || 5000;
app.use(logger); // 1. log every request
app.use(cors({ origin: process.env.CLIENT_ORIGIN || 'http://localhost:5173' })); // 2. let our React app call us
app.use(express.json({ limit: '100kb' })); // 3. turn a JSON body into req.body
app.get('/api/health', (req, res) => res.json({ ok: true }));
app.post('/api/echo', (req, res) => {
// req.body exists only because of express.json() above
res.status(201).json({ youSent: req.body });
});
app.listen(port, () => console.log(`API listening on http://localhost:${port}`));
app.use(fn) adds a middleware for every request. They run in the order you write them, then the matching route runs. Read the three in this file.
- The logger runs first, so that every request is logged, even ones that later fail.
- `cors` adds headers that tell the browser it is allowed to call this API from another address. A browser blocks a page at
localhost:5173from reading answers fromlocalhost:5000unless the server says that this origin is allowed. This is the browser's *same-origin policy*. Note that CORS only protects browsers: tools such as curl ignore it, so it is not a replacement for signing in. - `express.json()` reads the body of a request whose
Content-Typeisapplication/json, parses it, and puts the result inreq.body. Without itreq.bodyisundefined. Thelimitof 100 kb rejects absurdly large bodies, which protects the server from being flooded.
Try the pipeline
The route at the bottom of the file, POST /api/echo, shows that req.body works. Send it a body with curl: curl -i -X POST localhost:5000/api/echo -H "Content-Type: application/json" -d '{"hello":"world"}'. Then remove the Content-Type header and see that youSent becomes empty: Express only parses JSON when it is told the body is JSON.
Middleware for one route
A middleware can also be placed in front of a single route: app.get('/private', checkSomething, handler). You will use that for signing in (lesson 22), for rate limiting the login route (lesson 23) and for file uploads (lesson 36). Global middleware is for things every request needs; route middleware is for things only some routes need.
Your task
- Run the server and call
/api/health. Look at the logger line in the terminal. - Send the echo request with and without the
Content-Typeheader. Comparereq.body. - Write a new middleware
addRequestIdthat creates a random id withcrypto.randomUUID(), stores it onreq.id, and sets anX-Request-Idresponse header. Add it before the logger and include the id in the log line.
How to check it worked
- Each request prints one line with the method, path, status and time.
- The echo route returns what you sent only when the JSON header is present.
- Your responses have an
X-Request-Idheader.
Summary
- Middleware is a function
(req, res, next): handle the request or callnext(). - Order matters: they run top to bottom, so put
express.json()before the routes. - CORS tells browsers which sites may call the API. It is not security by itself.