Categories
Express Node.js

How does middleware work in Express?

5 min read

This article is an adapted excerpt from my book, Express API Validation Essentials. It teaches you a complete API validation strategy which you can start applying in your Express applications today.


This article is part of A Guide to Express API Validation.

The Express documentation tells us that "an Express application is essentially a series of middleware function calls". It sounds simple on the surface, but honestly, middleware can get pretty confusing. You’ve probably found yourself wondering:

  • Where is the right place to add this middleware in my application?
  • When should I call the next callback function, and what happens when I do?
  • Why does the order of middleware matter?
  • How can I write my own code for handling errors?

The middleware pattern is fundamental to building applications with Express, so you want to have a solid understanding of what middleware is and how it works.

In this article we’re going to dig into the middleware pattern. We’ll also look at the different types of Express middleware and how to effectively combine them when we build our applications.

Jump links

The middleware pattern

In Express, middleware are a specific style of function which you configure your application to use. They can run any code you like, but they typically take care of processing incoming requests, sending responses and handling errors. They are the building blocks of every Express application.

When you define a route in Express, the route handler function which you specify for that route is a middleware function:

app.get("/user", function routeHandlerMiddleware(request, response, next) {
	// execute something
});

(Example 1.1)

Middleware is flexible. You can tell Express to run the same middleware function for different routes, enabling you to do things like making a common check across different API endpoints.

As well as writing your own middleware functions, you can also install third-party middleware to use in your application. The Express documentation lists some popular middleware modules. There are also a wide variety of Express middleware modules available on npm.

Middleware syntax

Here is the syntax for a middleware function:

/**
 * @param {Object} request - Express request object (commonly named `req`)
 * @param {Object} response - Express response object (commonly named `res`)
 * @param {Function} next - Express `next()` function
 */
function middlewareFunction(request, response, next) {
	// execute something
}

(Example 1.2)

Note: You might have noticed that I refer to req as request and res as response. You can name the parameters for your middleware functions whatever you like, but I prefer verbose variable names as I think that it makes it easier for other developers to understand what your code is doing, even if they’re not familiar with the Express framework.

When Express runs a middleware function, it is passed three arguments:

  • An Express request object (commonly named req) – this is an extended instance of Node.js’ built-in http.IncomingMessage class.
  • An Express response object (commonly named res) – this is an extended instance of Node.js’ built-in http.ServerResponse class.
  • An Express next() function – Once the middleware function has completed its tasks, it must call the next() function to hand off control to the next middleware. If you pass an argument to it, Express assumes it to be an error. It will skip any remaining non-error handling middleware functions and start executing error handling middleware.

Middleware functions should not return a value. Any value returned by middleware will not be used by Express.

The two types of middleware

Plain middleware

Most middleware functions that you will work with in an Express application are what I call "plain" middleware (the Express documentation doesn’t have a specific term for them). They look like the function defined in the middleware syntax example above (Example 1.2).

Here is an example of a plain middleware function:

function plainMiddlewareFunction(request, response, next) {
	console.log(`The request method is ${request.method}`);

	/**
	 * Ensure the next middleware function is called.
	 */
	next();
}

(Example 1.3)

Error handling middleware

The difference between error handling middleware and plain middleware is that error handler middleware functions specify four parameters instead of three i.e. (error, request, response, next).

Here is an example of an error handling middleware function:

function errorHandlingMiddlewareFunction(error, request, response, next) {
	console.log(error.message);

	/**
	 * Ensure the next error handling middleware is called.
	 */
	next(error);
}

(Example 1.4)

This error handling middleware function will be executed when another middleware function calls the next() function with an error object e.g.

function anotherMiddlewareFunction(request, response, next) {
	const error = new Error("Something is wrong");

	/**
	 * This will cause Express to start executing error
	 * handling middleware.
	 */
	next(error);
}

(Example 1.5)

Using middleware

The order in which middleware are configured is important. You can apply them at three different levels in your application:

  • The route level
  • The router level
  • The application level

If you want a route (or routes) to have errors which they raise handled by an error handling middleware, you must add it after the route has been defined.

Let’s look at what configuring middleware looks like at each level.

At the route level

This is the most specific level: any middleware you configure at the route level will only run for that specific route.

app.get("/", someMiddleware, routeHandlerMiddleware, errorHandlerMiddleware);

(Example 1.6)

At the router level

Express allows you to create Router objects. They allow you to scope middleware to a specific set of routes. If you want the same middleware to run for multiple routes, but not for all routes in your application, they can be very useful.

import express from "express";

const router = express.Router();

router.use(someMiddleware);

router.post("/user", createUserRouteHandler);
router.get("/user/:user_id", getUserRouteHandler);
router.put("/user/:user_id", updateUserRouteHandler);
router.delete("/user/:user_id", deleteUserRouteHandler);

router.use(errorHandlerMiddleware);

(Example 1.7)

At the application level

This is the least specific level. Any middleware configured at this level will be run for all routes.

app.use(someMiddleware);

// define routes

app.use(errorHandlerMiddleware);

(Example 1.8)

Technically you can define some routes, call app.use(someMiddleware) , then define some other routes which you want someMiddleware to be run for. I don’t recommend this approach as it tends to result in a confusing and hard to debug application structure.

You should only configure middleware at the application level if absolutely necessary i.e. it really must be run for every single route in your application. Every middleware function, no matter how small, takes some time to execute. The more middleware functions that need to be run for a route, the slower requests to that route will be. This really adds up as your application grows and is configured with lots of middleware. Try to scope middleware to the route or router levels when you can.

Wrapping up

In this article we’ve learnt about the middleware pattern in Express. We’ve also learnt about the different types of middleware and how we can combine them when building an application with Express.

If you’d like to read more about middleware, there are a couple of guides in the Express documentation:

This article is part of A Guide to Express API Validation.


This article is an adapted excerpt from my book, Express API Validation Essentials. It teaches you a complete API validation strategy which you can start applying in your Express applications today.