Débutant25 min de lecture

Middleware

Understand Express middleware — the building blocks that process every request before it reaches your route handlers.

What is Middleware?

Middleware functions are the backbone of any Express application. They are functions that have access to the request object (req), the response object (res), and the next function (next) in the application's request-response cycle. Every time a request arrives at your Express server, it passes through a chain of middleware functions before it reaches your actual route handler. This chain is often called the middleware pipeline or middleware stack.

Think of middleware like a security checkpoint at an airport. Before you board your plane (the route handler), you pass through multiple checkpoints: ticket verification, ID check, baggage screening, boarding pass scan. Each checkpoint does one specific job and then either lets you continue to the next checkpoint or stops you entirely. Middleware works the same way.

Each middleware function can do any of the following four things:

  1. Execute any code — log information, measure performance, parse data
  2. Modify the request and response objects — add properties, set headers, attach user data
  3. End the request-response cycle — send a response and stop the chain (like a security guard turning you away)
  4. Call next() to pass control to the next middleware in the stack

The request flows through each middleware function in the order they were defined. If middleware A is registered before middleware B, the request hits A first, then B. This ordering is fundamental to how Express works. For example, you want your authentication middleware to run before your route handler — not after. If you placed it after, unauthenticated users could access protected data before the auth check even runs.

Middleware is what makes Express so flexible. Instead of one giant function that handles everything, you compose small, focused middleware functions. Need logging? Add a logging middleware. Need authentication? Add an auth middleware. Need CORS support? Add a CORS middleware. Each piece is independent and reusable.

Types of Middleware

Express supports five distinct types of middleware, each serving a different purpose. Understanding these types will help you structure your applications effectively.

1. Application-level Middleware — Bound to the Express app instance using app.use() or app.METHOD(). These run for every request (or specific routes). Example: app.use(express.json()) runs on every request to parse JSON bodies.

2. Router-level Middleware — Works exactly like application-level middleware, but is bound to an instance of express.Router(). This lets you apply middleware only to a group of routes. For example, you might have an authRouter that applies authentication middleware only to routes that need it, like /dashboard or /profile, without affecting public routes like /login or /about.

3. Error-handling Middleware — Has a special signature with four parameters: (err, req, res, next). Express recognizes middleware as error-handling specifically because it has four arguments. When any middleware or route handler calls next(error) or throws an error, Express skips all normal middleware and jumps straight to the first error-handling middleware it finds. This is your centralized error handler.

4. Built-in Middleware — Express ships with a few middleware functions out of the box. express.json() parses incoming JSON request bodies. express.urlencoded() parses URL-encoded form data. express.static('public') serves static files (HTML, CSS, images) from a directory. These cover the most common needs without installing anything extra.

5. Third-party Middleware — The Express ecosystem has thousands of middleware packages. cors handles Cross-Origin Resource Sharing headers. morgan provides HTTP request logging. helmet sets security-related HTTP headers. compression compresses response bodies. express-rate-limit limits repeated requests. You install these with npm and plug them in with app.use().

Middleware Pipeline Example

javascript
const express = require('express');
const app = express();

// ---- Middleware 1: Logger ----
// Runs on EVERY request because it uses app.use() with no path
function logger(req, res, next) {
  console.log(`${new Date().toISOString()} ${req.method} ${req.url}`);
  next(); // Pass control to the next middleware
}
app.use(logger);

// ---- Middleware 2: Body parser ----
// Built-in middleware to parse JSON request bodies
app.use(express.json());

// ---- Middleware 3: Auth check (applied to specific routes) ----
function requireAuth(req, res, next) {
  const token = req.headers.authorization;
  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
    // Notice: we do NOT call next() here — the chain stops
  }
  // Token exists, attach user info to request
  req.user = { id: 1, name: 'Alice' };
  next(); // Continue to the route handler
}

// ---- Public route (no auth middleware) ----
app.get('/public', (req, res) => {
  res.json({ message: 'Anyone can see this' });
});

// ---- Protected route (auth middleware applied) ----
// requireAuth runs BEFORE the route handler
app.get('/dashboard', requireAuth, (req, res) => {
  res.json({ message: `Welcome ${req.user.name}` });
});

// ---- Error-handling middleware (must be last) ----
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: 'Something went wrong' });
});

// Request flow for GET /dashboard:
// 1. logger (logs the request)
// 2. express.json() (parses body — nothing to parse for GET)
// 3. requireAuth (checks token, attaches user)
// 4. route handler (sends response)

The next() Function

The next() function is the glue that connects middleware together. It is not a built-in JavaScript feature — Express passes it as the third argument to every middleware function. When you call next(), Express looks at its internal middleware stack and invokes the next function in line.

There are three ways to use next():

1. next() with no arguments — This passes control to the next middleware. It is the normal flow. If you have middleware A, B, and C in that order, calling next() in A takes you to B, and calling next() in B takes you to C.

2. next(error) with an error argument — This skips all remaining normal middleware and route handlers, jumping directly to the first error-handling middleware (the one with four parameters). This is how you trigger your centralized error handler. For example: next(new Error('Database connection failed')).

3. Not calling next() at all — If your middleware does not call next() and does not send a response, the request hangs indefinitely. The client waits and waits until their request times out. This is one of the most common middleware bugs. Every middleware must either call next() or send a response — there is no third option.

Order matters critically. Middleware executes in the exact order you define it with app.use(). If you place your logging middleware after your routes, it will never log anything because the route handler sends the response first. If you place your error handler before your routes, it will never catch errors because it was already passed. The standard order is: body parsing first, then logging, then authentication, then routes, and finally error handling at the very end.

One subtle point: next() does not stop execution of the current middleware. Code after next() still runs. If you want to stop, use return next() to prevent the remaining code in that middleware from executing.

Common Middleware Patterns

Here are the middleware patterns you will see and write in virtually every Express application:

Request Logging — Log every incoming request with method, URL, and timestamp. Useful for debugging and monitoring. The popular morgan package does this, but you can write your own in a few lines: check req.method, req.url, and Date.now().

Authentication and Authorization — Verify that the user is who they claim to be (authentication) and that they have permission to access the resource (authorization). Typically: extract a JWT or session token from the Authorization header, verify it, decode the user information, and attach it to req.user. If the token is missing or invalid, send a 401 Unauthorized response without calling next().

CORS (Cross-Origin Resource Sharing) — Browsers block requests from one domain to another by default. CORS middleware sets the Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers response headers to tell the browser which origins are allowed. The cors npm package handles this automatically.

Body Parsing — Incoming request bodies arrive as raw streams of data. Body-parsing middleware reads the stream and converts it into a usable JavaScript object on req.body. express.json() handles JSON bodies, express.urlencoded() handles form submissions.

Static File Servingexpress.static('public') serves files directly from a directory. When a request comes in for /style.css, Express checks the public folder, finds style.css, and sends it back. No route handler needed.

Rate Limiting — Prevent abuse by limiting how many requests a client can make in a given time window. Typically implemented by tracking IP addresses and request counts. Returns a 429 Too Many Requests status when the limit is exceeded.

Request Validation — Check that incoming data meets your requirements before it reaches the route handler. Validate that required fields exist, types are correct, and values are within acceptable ranges. Return 400 Bad Request with details if validation fails.

What happens if middleware doesn't call next() or send a response?

Prêt à pratiquer ?

Crée ton compte gratuit pour accéder à l'éditeur de code interactif, lancer les défis et suivre ta progression.