Skip to content

Express JS Middleware Tutorial

Introduction

In this article, I’m going to show you what Express JS middleware is and how to write custom middleware functions for Express.

What is Express JS Middleware?

When someone asks you a question, you hear the question, think about it for a few moments and then answer the question. And this middle step, the thinking, is similar to what an Express JS middleware does.

Write a Middleware Function

In terms of code, an Express JS middleware is a function that takes three arguments, “request”, “response”, and “next”. The “request” and “response” are objects, which we can read and modify in the function. And the “next” is a function that triggers the next middleware function to run.

function firstMiddleware(req, res, next) { 
}

We could also use the arrow function syntax to define the middleware.

const firstMiddleware = (req, res, next) => {
};

I’m going to print the message “First middleware” to the console. I will then call the next middleware in the stack.

const firstMiddleware = (req, res, next) => {
  console.log('First middleware');
  next();
};

Use a Middleware in Express

Run Middleware for Every Request

To run a middleware in response to a request, pass it to “app.use”, and it will run the middleware function every time the application receives a request.

app.use(firstMiddleware);

I will start the server in the Terminal using “npm start”. I will then open the browser and go to “localhost:8080”. Now, if we check the Terminal, we will see the middleware output.

Middleware “Next” Function

Every middleware function must either end the request-response cycle or call “next” to pass control to the next middleware. Otherwise, the request will be left hanging.
Let’s remove the “next” function call to see what will happen.

const firstMiddleware = (req, res, next) => {
  console.log('First middleware');
};

If we open a new tab in the browser and go to “localhost:8080”, the page will not load. Express relies on “next” to know when to move forward.

Middleware Order is Important

Express will execute the middleware functions in the order in which we load them. So, the order of loading middleware functions is important. Let’s write another middleware function to print another message to the console.

const secondMiddleware = (req, res, next) => {
  console.log('Second middleware');
  next();
};

app.use(secondMiddleware);

If I refresh the browser and check the Terminal, we will see “First middleware” and then “Second middleware” logged to the console.

Run Middleware for a Specific Route

To execute an Express middleware only for a specific route, pass a route argument before the middleware function. Here, we want to run the “firstMiddleware” only for the “/first” route.

app.use('/first', firstMiddleware);

If I refresh the root route of the application in the browser, we will not see the “First middleware” message in the Terminal. Let’s go to “/first” in the browser, and you will see both messages logged in the Terminal.

Run Middleware for a Specific HTTP Method

We can also limit the middleware to run only for a specific HTTP method. For example, to execute a middleware function only for “get” requests, use the “app.get” instead of the “app.use” function.

app.get('/', (req, res) => {
  res.send('Hello Express!');
});

Read and Modify the Request and Response Objects

Let’s add a new property called “timestamp” to the “request” object in the “firstMiddleware” function. I will then change the “firstMiddleware” to print the request method, request URL and request timestamp to the console.

const firstMiddleware = (req, res, next) => {
  req.timestamp = Date.now();
  console.log(
    `A ${req.method} request received on ${req.originalUrl} at ${req.timestamp}`,
  );
  next();
};

Pass Data Between Middleware Functions

Any changes you make to the request and response objects will be available in the next middleware functions. So now we can read the “timestamp” property in the “secondMiddleware”.

const secondMiddleware = (req, res, next) => {
  console.log(`Before response to the request received at ${req.timestamp}`);
  next();
};

Run the Middleware After Sending Response

Suppose we want to run another middleware function after sending the response to the user. Sending the response to the user will end the request-response cycle. So the succeeding middleware function will not run. To solve this, we need to add the “next” function call after calling the “send” function.

app.get('/', (req, res, next) => {
  res.send('Hello Express!');
  next();
});

const thirdMiddleware = (req, res, next) => {
  console.log(`After response to the request received at ${req.timestamp}`);
  next();
};

app.use(thirdMiddleware);

Router Middleware

The middleware functions we discussed until now are application-level middleware functions. A router-level middleware is the same as application-level middleware, except it is bound to an instance of the Express router. Just create an instance of Express router and then replace the “app” with “router”.

const router = express.Router();

const firstMiddleware = (req, res, next) => {
  req.timestamp = Date.now();
  console.log(
    `A ${req.method} request received on ${req.originalUrl} at ${req.timestamp}`,
  );
  next();
};

router.use(firstMiddleware);

const secondMiddleware = (req, res, next) => {
  console.log(`Before response to the request received at ${req.timestamp}`);
  next();
};

router.use(secondMiddleware);

router.get('/', (req, res, next) => {
  res.send('Hello Express!');
  next();
});

const thirdMiddleware = (req, res, next) => {
  console.log(`After response to the request received at ${req.timestamp}`);
  next();
};

router.use(thirdMiddleware);

And finally, load the router using the “app.use”.

app.use(router);

Difference Between Router-Level and Application-Level Middleware

So, what’s the difference between an application-level and a router-level middleware, you may ask? Router-level middleware executes for the routes served by a specific router, and application-level middleware handles the requests received by the application.

An example use case could be an application with one router for web pages routes and another router that handles API routes, which need a valid user. You would then mount the authentication middleware for the API router only.

GitHub Repo

Here is the GitHub repository where you can find the complete source code of this article, clone the project and run it on your machine.