PLAINFINCH learn by building

Middleware

You will be able to write middleware that every request passes through and place it in the right order.

What is middleware?

Middleware is a function that Express runs on a request before, or instead of, your route handler. It gets the same req and res as a route, plus a third argument, next. It can read the request, add something to it, add headers to the response, answer the request itself, or call next() to pass it along to whatever comes next.

You have used middleware since the lesson on methods. express.json() reads the body and puts it on req.body. express.static('public') answers requests for files. cookieParser() fills req.cookies. requireLogin in the members area checks the session and either redirects or calls next(). Each is a function with the shape (req, res, next); Express only cares where you put it.

The point of middleware is to write a rule once. Without it, every route that needs a logged-in member would repeat the same five lines of session checking, and the one route where someone forgot them would be the one that leaks. With it, the rule lives in one function and each route opts in by name, or every route gets it from a single app.use() line near the top.

How does a request travel through the line?

Think of the bakery's kitchen door. An order comes in and passes a row of people: one writes it in the book, one checks the item exists, one bakes it. Any of them can stop the order and send a note back. Otherwise each hands it to the next person. Express keeps one list of everything you registered with app.use(), app.get(), app.post() and the rest, in the order the lines appear in server.js. A request starts at the top and moves down.

For each entry, Express checks whether it applies: app.use() matches every method and any path that starts with its path (/ when you leave it out), while app.get('/menu') matches only GET /menu. When an entry matches, its function runs. Calling next() moves on. Sending a response ends the trip. If the request reaches the end of the list without an answer, Express sends its standard 404 "Cannot GET" page.

// Runs for every request, then hands it on.
function logRequests(req, res, next) {
  console.log(`${req.method} ${req.path}`);
  next();
}

app.use(logRequests);       // 1: every request
app.use(express.json());    // 2: every request with a JSON body
app.get('/menu', showMenu); // 3: only GET /menu
// ...no match: Express sends 404

Why does the order of app.use() matter?

Look at the starting code. express.json() is registered at the bottom, after POST /orders. A JSON order reaches the route first, the route reads req.body, finds nothing, and answers 400. The parser would have run next, but the request never gets that far. Moving one line up fixes it.

The same rule explains most middleware bugs. A logger placed after the routes only logs requests that no route answered. A 404 handler placed above express.static hides every file. A login check placed after the private route protects nothing. When something in a middleware "doesn't run", the first thing to check is whether a line above it already answered.

  • Near the top: loggers, request ids, security headers, body parsers and cookie parsing.
  • Then: static files and your routes, with route-level checks such as requireLogin attached to the routes that need them.
  • At the very end: the 404 handler, then the error handler with four arguments.
FigurePOST /orders with item Rye loaf and qty 2. In the starting order the route runs before express.json, finds no body and answers 400. With logRequests and express.json moved above the routes, the body is read and the route answers 201.POST /orders with item Rye loaf and qty 2. In the starting order the route runs before express.json, finds no body and answers 400. With logRequests and express.json moved above the routes, the body is read and the route answers 201.
Express walks the list from the top and stops at the first entry that answers. In the starting file the order route answers before the JSON parser ever runs, so moving one line up turns the 400 into a 201.

What happens when middleware forgets next()?

Every middleware must do one of two things: send a response, or call next(). If it does neither, the request sits there. The browser waits, the Requests view shows **No reply**, and on a real server the connection eventually times out. Nothing is printed in the Log, because nothing went wrong from Express's point of view.

The opposite mistake is doing both. A check that sends 403 and then falls through to next() lets the route run anyway, which may save data or reveal it, and the route's own answer then fails with ERR_HTTP_HEADERS_SENT. Write the refusing branch as return res.status(403).send(…). The return makes it impossible to reach the next() below.

When should middleware apply to one route only?

Pass middleware as extra arguments to a route, and it runs only there: app.post('/orders', requireJson, handler). Express runs them left to right. Use this for checks that belong to particular routes: login on private pages, a strict rate limit on the login form, input checks on one form. You can list several, and you can put shared ones in an array.

A path in app.use() gives you something in between. app.use('/admin', requireStaff) runs for every request whose path starts with /admin, whatever the method. Routers work the same way: app.use('/api', apiRouter) sends everything under /api to the router, which has its own list of middleware and routes.

app.post('/orders', requireJson, express.json(), saveOrder);
app.use('/admin', requireLogin, requireStaff);

How is error-handling middleware different?

A middleware with four arguments, (err, req, res, next), is an error handler. Express skips it during normal requests. When a route throws, or calls next(err), Express jumps straight down the list to the next error handler, skipping ordinary middleware on the way. In Express 5, an async handler that rejects is passed along the same way, so await inside routes no longer needs a try/catch just to reach the error handler.

The four arguments matter: Express counts them. A function written as (err, req, res) looks like an ordinary middleware with oddly named parameters, and Express treats it as one. Put the error handler last, log the full error there, and send visitors a short message without the stack trace.

How do you write a maintenance switch?

A switch is a middleware near the top that checks a setting. When the bakery is closed for maintenance, it answers every request with 503 Service Unavailable and a Retry-After header; otherwise it calls next() and the site runs as usual. Read the setting from an environment variable, so turning it on doesn't need a code change. Leave a way in for checks, such as letting /health through, so the host can still see the server is alive.

app.use((req, res, next) => {
  if (process.env.MAINTENANCE === 'on' && req.path !== '/health') {
    return res.status(503).set('Retry-After', '3600').send('<h1>Back soon</h1>');
  }
  next();
});

What should you check when an AI writes middleware?

Read every middleware for its two exits. Each branch should end with a response or next(), and every response inside an if should have return in front. Then read the order of the app.use() lines from top to bottom, as a request would. Body parsers before routes, checks before what they protect, the 404 handler and error handler last.

Generated code also tends to put everything on app.use(), including checks that belong on a few routes, and sometimes registers the same parser twice. In the practice server, send a request and watch the Log line from your logger: if it is missing, something above it answered first. If the Requests view says **No reply**, a middleware swallowed the request.

Field notes

  • Middleware is a function that runs on a request before, or instead of, your route.
  • Each middleware must either send a response or call next(), or the request hangs.
  • Express runs middleware in the order you register it, so place it above the routes.
  • express.json() must come before routes that read req.body.

Easy to get wrong

Without next(), every request stops in the logger. Nothing answers, so every page waits forever and the Requests view shows **No reply**.

Express runs entries in file order. The route answers before the parser ever runs, so req.body is undefined inside it.

Express recognises an error handler by its four parameters. With three, it is treated as ordinary middleware, receives the request in the err slot, and never sees real errors.

Questions people ask

Can middleware add things to req?

Yes, and that is a common use. requireLogin sets req.memberId, a request-id middleware can set req.id, and later handlers read them. Pick names that won't clash with Express's own properties, such as body, params or query.

Does middleware slow the server down?

Each function adds a little work to every request it runs on. For small checks and loggers it is too little to notice. Keep slow work, such as database lookups, in route-level middleware on the routes that need it instead of on every request, including images and stylesheets.

What is the difference between app.use and app.all?

app.use('/admin', fn) matches /admin and every path below it, such as /admin/orders. app.all('/admin', fn) matches every method but only the exact path /admin. Use app.use for middleware that covers a section of the site.

Where do packages like cookie-parser fit?

They are middleware too. Each one exports a function that returns (req, res, next), which is why you call cookieParser() inside app.use(). Order them like your own: before the routes that need what they add.

Keep going