Express Middleware: express.json, CORS and a Request Logger

MERN Stack Full Course: MongoDB, Express, React and Node.js · Node.js and Express · 4 min read · Updated

The middleware pipeline

  1. Middleware are functions that every request passes through, one after another.
  2. The logger records the method, the path and later the time taken, and then calls next().
  3. express.json turns the JSON body into req.body. Without it, req.body is empty.
  4. Finally the route handler runs and sends the response. cors, added earlier in the line, lets the browser app call this API.
  5. If a middleware does not call next() or reply, the request hangs. Order matters.

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:5173 from reading answers from localhost:5000 unless 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-Type is application/json, parses it, and puts the result in req.body. Without it req.body is undefined. The limit of 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

  1. Run the server and call /api/health. Look at the logger line in the terminal.
  2. Send the echo request with and without the Content-Type header. Compare req.body.
  3. Write a new middleware addRequestId that creates a random id with crypto.randomUUID(), stores it on req.id, and sets an X-Request-Id response 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-Id header.

Summary

  • Middleware is a function (req, res, next): handle the request or call next().
  • 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.

Download the solution (mern-lesson-10.zip)

Frequently asked questions

Why does my request hang forever?

A middleware or route neither sent a response nor called next(). Every path through your code must end in a response or a call to next.

What is CORS and why do I get a CORS error?

Browsers refuse to let a page read answers from another origin (different address or port) unless the server allows it with CORS headers. In this course the server allows the React address, and in production both are served from one origin so no CORS is needed.

Does CORS protect my API from attackers?

No. CORS is enforced by browsers only. Anyone can call your API with curl or a script. Protection comes from signing in, authorization checks, validation and rate limiting.