How to Make Your Very Own Express Middleware
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.
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();
});
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.
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);
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.
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.
const express = require('express');
const timeout = require('connect-timeout');
const app = express();
app.use(timeout('5s'));
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.
app.get('/treasury', (req, res, next) => {
console.log('🎉 You\'ve found the secret treasure 💰💰💰, step to the next level...');
next();
});
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.
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]);
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.
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.
const setupUser = (req, res, next) => {
req.user = getUser(req.headers.token);
next();
}
app.use(setupUser);
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.
const setAPIHeaders = (req, res, next) => {
res.setHeader('X-API-Version', '2.0');
next();
}
app.use(setAPIHeaders);
You can also set up middleware functions that can be configured via function arguments.
const setAPIHeader = version => {
return (req, res, next) => {
res.setHeader('X-API-Version', version);
next();
};
};
app.use(setAPIHeader('3.0'));
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.
app.route('/secret')
.get(hidden, (req, res) => {
res.json({ message: 'Now you have access' });
});
This calls a function called hidden
, then it continues into the second argument. So let’s defined the middleware.
const hidden = (req, res, next) => {
if (req.headers['x-preview-feature'] === 'true') {
next();
} else {
res.json({});
}
};
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.
const hidden = (req, res, next) => {
if (req.headers['x-preview-feature'] === 'true') {
next();
}
response.json({});
};
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.
const hidden = (req, res, next) => {
if (req.headers['x-preview-feature'] === 'true') {
return next();
}
res.json({});
};
Now finally let’s see how everything works in Postman. The route will only accept a true
value.
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.
const express = require('express');
const morgan = require('morgan');
const app = express();
app.use(morgan(':method :url :status - :response-time ms'));
For example, if you use the above config, it will generate a log like the one below.
CORS
Another commonly used Express middleware is CORS. Simply add the following to enable all CORS request for your Express server.
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
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.
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());
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
.
const express = require('express');
const helmet = require('helmet');
const app = express();
// Use default values
app.use(helmet());
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.
const express = require('express');
const compression = require('compression');
const app = express();
// compress all responses
app.use(compression());
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 ☕
Rocket Launch Your Career
Speed up your learning progress with our mentorship program. Join as a mentee to unlock the full potential of Webtips and get a personalized learning experience by experts to master the following frontend technologies: