Express Middleware Explained: next(), Logging and express.json()

Node.js and Express: Build a Back End API from Scratch · Express · 5 min read · Updated

Middleware chain

  1. A request comes in. Express runs your middleware in the order you wrote it.
  2. The logger notes the start time and calls next() to pass the request on.
  3. express.json() reads the body, parses the JSON and stores it in req.body.
  4. Finally the route handler runs and sends the response. If a middleware had responded earlier, the chain would have stopped there.

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.body is undefined in Express 5, not an empty object. A line such as req.body.title then 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

  1. Add the logger and express.json() to your server and the /api/echo route. Send a JSON body with curl and check the log line.
  2. Move the logger below the echo route and see why it stops logging that route.
  3. Send an invalid JSON body such as {bad and see the HTML error. Keep this in mind for lesson 10.
  4. Write a middleware called addRequestId that sets req.id to a random value from crypto.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/echo returns the JSON you sent.
  • Moving the logger after the route proves to you that order matters.

Frequently asked questions

What happens if middleware never calls next()?

If it also sends no response, the request hangs until the client times out. A middleware must either respond or call next().

Why is req.body undefined in my route?

The body parser did not run or did not match. Check that express.json() is registered before the route, and that the client sends the Content-Type: application/json header.