How to Make Your Very Own Express Middleware

How to Make Your Very Own Express Middleware

Demystifying a simple concept in Express
Ferenc AlmasiLast updated 2021 November 11 • Read time 8 min read
Express is made up of series of middleware functions. Yet the word “middleware” may sound intimidating. In this tutorial you'll learn every about Middleware
  • twitter
  • facebook

Express is essentially made up of series of middleware functions. Yet the word “middleware” may sound intimidating at first. In reality, they are nothing more than functions that have access to special objects. The request and response object, along with the next middleware function in the chain. This is usually referenced as next.

Their lifecycle is pretty straightforward too. They can execute any code, make changes to the request/response objects, or finish their job. They can do this by ending the cycle, or calling the next middleware function in the stack.


How Do They Work?

When you make a request to Express, you either have to end the request-response cycle — for example by returning a response — or call the next middleware. Otherwise, the request will be left hanging.

Copied to clipboard! Playground
app.get('/middleware', (request, response, next) => {
    // Ending request-response cycle by returning a response from the server
    response.json({ message: 'Return a response' });
    
    // or calling the next middleware
    next();
});
server.js

Here, the second parameter passed to app.get is in fact a middleware function. A middleware function expects three parameters:

  • request: The request object contains the HTTP request properties. Such as parameters, request body, or query string.
  • response: The response object represents an HTTP response that you can send, once an HTTP request is received.
  • next: Callback to the next middleware function, usually called next by convention.

Sequence of Middleware

We have to note that when dealing with multiple middleware, their order matter. Take a look at the following example.

Copied to clipboard! Playground
const express = require('express');
const app = express();

const logOne = (req, res, next) => { 
    console.log('1️⃣');
    next();
};

const logTwo = (req, res, next) => { 
    console.log('2️⃣');
    next();
};

const logThree = (req, res, next) => { 
    console.log('3️⃣');
    next();
};

app.use(logThree);
app.use(logTwo);
app.use(logOne);
server.js

If we make a call to the server, the middleware functions will be executed in order. This means we get the following in the console.

The order of middleware functions matter

Also note that for all of them you have to call next(), otherwise the request will never have a response. Here we specified the functions globally. You can also do it for individual routes.

Global Middleware

To recap, global middleware functions are functions that execute for every route. You can define them on the app variable.

Copied to clipboard! Playground
const express = require('express');
const timeout = require('connect-timeout');

const app = express();

app.use(timeout('5s'));
server.js

We can use the above middleware to timeout any request that takes longer than 5 seconds to finish.

Route Middleware

Route middleware functions, on the other hand, is used for specifying functions that execute for specific routes.

Copied to clipboard!
app.get('/treasury', (req, res, next) => {
    console.log('🎉 You\'ve found the secret treasure 💰💰💰, step to the next level...');
    next();
});
routes.js

This middleware will only get executed once we make a request to /treasury. You can also have multiple middleware for the same route. You can do this by passing an array of callback functions.

Copied to clipboard! Playground
const openFirstChest = (req, res, next) => {
    console.log('💸');
    next();
};

const openSecondChest = (req, res, next) => {
    console.log('💰');
    next();
};

const congratulations = (req, res, next) => {
    res.send('You\'ve found the secret treasure 🎉');
};

app.get('/treasury', [openFirstChest, openSecondChest, congratulations]);
routes.js

Now let’s see some examples of how you can create your own practical middleware functions.


The Project Setup

First, you want to have a basic Express server set up. To save some time so we can focus on the important part, I’m using this GitHub repository as a boilerplate. It is from a previous tutorial, which explains how you can build your own REST API from scratch.

We won’t need any external dependencies as we can achieve everything with simple, plain old JavaScript.

Looking to improve your skills? Check out our interactive course to master JavaScript from start to finish.
Master JavaScriptinfo Remove ads

Extending Requests

Let’s start by looking at an application-level middleware. We will be able to use it to extend the request object. Say your server gets a request that contains a token. You want to have every user information available for all requests. You already have the token available in your header. This means we can do it quite simply with the following.

Copied to clipboard! Playground
const setupUser = (req, res, next) => {
    req.user = getUser(req.headers.token);
    next();
}

app.use(setupUser);
server.js
the getUser function fetches user information based on the passed token

It ensures that the next middleware function will always receive a user object. With all the available user information.


Extending Responses

We can do the same with the response object. If you want to expose more data about your server, you can simply extend it with additional information.

Copied to clipboard! Playground
const setAPIHeaders = (req, res, next) => {
    res.setHeader('X-API-Version', '2.0');
    next();
}

app.use(setAPIHeaders);
server.js

You can also set up middleware functions that can be configured via function arguments.

Copied to clipboard! Playground
const setAPIHeader = version => {
    return (req, res, next) => {
        res.setHeader('X-API-Version', version);
        next();
    };
};

app.use(setAPIHeader('3.0'));
server.js

In this case, you need to return a new middleware function which takes in the three necessary arguments.


Hiding Routes

Lastly, let’s see another example that we can use on individual routes. Say you want to authorize access to a resource or you simply want to hide it.

Create a new route called /secret and pass a custom middleware as the first argument to its get handler.

Copied to clipboard!
app.route('/secret')
    .get(hidden, (req, res) => {
        res.json({ message: 'Now you have access' });
    });
routes.js

This calls a function called hidden, then it continues into the second argument. So let’s defined the middleware.

Copied to clipboard! Playground
const hidden = (req, res, next) => {
    if (req.headers['x-preview-feature'] === 'true') {
        next();
    } else {
        res.json({});
    }
};
routes.js

Here we have a simple check. If the header exists and its value is true, we can move onto the next middleware which is also the last one. Otherwise, we can send back an empty response. Again, we either call the next middleware or send back a response. We don’t want to left Express hanging.

One extra note: don’t place additional logic after a next call. You can run into issues where you try to send headers more than once. Imagine you change the above code to the following.

Copied to clipboard! Playground
const hidden = (req, res, next) => {
    if (req.headers['x-preview-feature'] === 'true') {
        next();
    }
    
    response.json({});
};
routes.js

In this case, if the header is set, you will start calling the next middleware function until the very last returns a response. But after that, execution jumps back to this function since it’s not a return statement. Then Express will try to send a response again, which it already did. A simple way to fix this is by returning the next middleware instead.

Copied to clipboard! Playground
const hidden = (req, res, next) => {
    if (req.headers['x-preview-feature'] === 'true') {
        return next();
    }
    
    res.json({});
};
routes.js

Now finally let’s see how everything works in Postman. The route will only accept a true value.

Testing out our custom middleware

What Are Some Other Use-Cases?

There are countless useful Express middleware out there. Each designed for a different task. You’ve probably met or even used some of them before. And there are some you definitely want to make use of. Let’s see a couple.

Morgan

Morgan is the to-go middleware if you want to have logging in place. You can use custom log functions, but it also comes with some predefined tokens.

Copied to clipboard! Playground
const express = require('express');
const morgan = require('morgan');

const app = express();

app.use(morgan(':method :url :status - :response-time ms'));
server.js

For example, if you use the above config, it will generate a log like the one below.

Example logs generate with Morgan

CORS

Another commonly used Express middleware is CORS. Simply add the following to enable all CORS request for your Express server.

Copied to clipboard! Playground
const express = require('express');
const cors = require('cors');

const app = express();

app.use(cors());
server.js

You can also enable it for a single route, using a route level middleware.

Body-parser

If you have user input that is passed for your server, body-parser is one you should definitely use. This is because the req.body object cannot be trusted as users could spoof it. Body-parser will parse incoming request bodies to make your requests safer.

Copied to clipboard! Playground
const express = require('express');
const bodyParser = require('body-parser');
 
const app = express();

// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }));
 
// parse application/json
app.use(bodyParser.json());
server.js

Helmet

Using Helmet is another great way to secure your Express app. It sets various HTTP headers to add XSS protection, prevent clickjacking, and many more. To use its default values you can add the following to your server.js.

Copied to clipboard! Playground
const express = require('express');
const helmet = require('helmet');

const app = express();

// Use default values
app.use(helmet());
server.js

Compression

Lastly, compression can be used to optimize your server. It compresses requests to save some precious bytes. It supports deflate and gzip. To compress all responses, place the following before your routes.

Copied to clipboard! Playground
const express = require('express');
const compression = require('compression');

const app = express();

// compress all responses
app.use(compression());
server.js
Looking to improve your skills? Check out our interactive course to master JavaScript from start to finish.
Master JavaScriptinfo Remove ads

Summary

That is all you need to know to get yourself familiar with middleware. As mentioned in the beginning, Express itself is made up of different middleware functions. Once you understand the flow, they can be a really powerful tool, because of their modularity. If you have any tips on using middleware functions in Express, don’t hesitate to share them in the comments. Thank you for taking the time to read this article. Happy coding!

Get yourself an Expresso Sticker

That's right, I'm drinking Expresso
  • twitter
  • facebook
Did you find this page helpful?
📚 More Webtips
Frontend Course Dashboard
Master the Art of Frontend
  • check Access 100+ interactive lessons
  • check Unlimited access to hundreds of tutorials
  • check Prepare for technical interviews
Become a Pro

Courses

Recommended

This site uses cookies We use cookies to understand visitors and create a better experience for you. By clicking on "Accept", you accept its use. To find out more, please see our privacy policy.