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.
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
asrequest
andres
asresponse
. 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 thenext()
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 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.
Tired of wasting time reading Node.js blog posts which don't actually help you improve your projects?
Sign up to my weekly-ish newsletter and I'll let you know when I publish a new blog post which helps solve your real developer problems. I'll also send you an awesome tip so we can level up together, as well as a handful of excellent things by other people.
Latest comments (1)
Thank you for the post, it really help so much