How To Use Express Middleware

Middleware is a technique that allows software developers to make specific code run anytime an Express route is called. The middleware pattern makes it possible to execute code between a server receiving a request and sending a response. Hence the name Middleware, as it lives in the middle. Express uses middleware to apply behavior to request and response objects. Express middleware can do things like attach information to a response, inspect a request, pass a request and response to a different middleware, or perform some other logic. Anything that can be done in a JavaScript function can be applied to middleware in Express.


app.use()

When you see the familiar app.use() syntax in an Express project, you know that a Middleware is in use. The way it works is to pass a callback function into app.use(), and that callback function will be executed on every request made. Let’s look at a super simple example of middleware. The relevant code is highlighted below.

const express = require('express');
const app = express();

const PORT = process.env.PORT || 4001;

app.listen(PORT, () => {
    console.log(`Server is listening on port ${PORT}`);
});

app.use(myMiddleware);

app.get('/', (req, res, next) => {
    res.send('You have reached the home page');
});

function myMiddleware(req, res, next) {
    console.log('Just ran myMiddleware!');
}

By starting the server with node app.js and then visiting the / homepage three times, we can see that the middleware fired on each page request and logged out the message “Just ran myMiddleware!” to the console each time.

PS C:\node\express> node app.js
Server is listening on port 4001
Just ran myMiddleware!
Just ran myMiddleware!
Just ran myMiddleware!

The code above has the callback broken out into a separate function. This is just for learning purposes. You would typically be using a shorthand, or arrow, function syntax which would look more like this code here:

const express = require('express');
const app = express();

const PORT = process.env.PORT || 4001;

app.listen(PORT, () => {
    console.log(`Server is listening on port ${PORT}`);
});

app.use((req, res, next) => {
    console.log('Just ran myMiddleware!');
    next();
});

app.get('/', (req, res, next) => {
    res.send('You have reached the home page');
});

next()

You may have noticed the refactored version of the middleware just above also has a next() function call in it. What is that all about? The next() function is actually very important in Express middleware. Express describes itself as a series of middleware function calls and the next() function is what keeps the application flow moving from one middleware to the next. This makes Express perfect not only for routing to communicate with various endpoints, but we can also perform the application logic we need by implementing the necessary middleware.

express middleware next


Stacking Middleware

You can pass the application request-response cycle through as many middleware functions as needed in order to complete the required job. Let’s see an example of how to stack middleware in Express.

const express = require('express');
const app = express();

const PORT = process.env.PORT || 4001;

app.listen(PORT, () => {
    console.log(`Server is listening on port ${PORT}`);
});

app.use((req, res, next) => {
    console.log("It's time for planting peppers.");
    next();
});

app.get('/pepper/:type', (req, res, next) => {
    console.log("The gardener is planting a seed!");
    next();
});

app.get('/pepper/:type', (req, res, next) => {
    console.log(`The gardener has planted some ${req.params.type} peppers.`);
    res.status(200).send();
    next()
});

app.get('/pepper/:type', (req, res, next) => {
    console.log("Planting is complete!");
    next();
});

Accessing http://localhost:4001/pepper/hot

It's time for planting peppers.
The gardener is planting a seed!
The gardener has planted some hot peppers.
Planting is complete!

In the above code, the routes are called in the order that they appear in the file, provided the previous route called next() and thus passed control to the next middleware. Note that each middleware makes sure to call the next() function at the end of it. An Express middleware has three parameters being req, res, and next. The sequence is expressed by a set of callback functions called in order after each middleware performs its purpose. The next() function should always get explicitly called in the last part of the middleware’s body. This is how Express hands off the processing of the request and the construction of the response to the next middleware in the stack.


A Closer Look At (req, res, next)

The function signature of an Express middleware, (req, res, next), is actually the same exact format as we’ve been using for Express Routes. This is because Express routes are in fact also middleware! Every route in an Express application is also a middleware function handling the request and response objects at that part of the stack. Express routes are also able to send a response body and status code as well as close the connection. This is possible because Express routes are middleware, and all Express middleware functions have access to the request, the response, and the next middleware in the stack.


Route Specific Middleware

Optional arguments for functions are placed in square brackets ([]) and when we look at the function signature for app.use() we see the following:

app.use([path,] callback [, callback...])

This means that app.use() can take an optional path parameter as its first argument. If this is specified, then the middleware only runs for that specific path. This is important because up until now we have only seen middleware that runs on every single request and response and of course we may not want that. This little application below makes use of both global middleware and route-specific middleware and each is highlighted.

const express = require('express');
const app = express();

app.use(express.static('public'));

const PORT = process.env.PORT || 4001;

const bagOfPeppers = {
    mystery: {
        number: 4
    },
    yummy: {
        number: 5
    },
    lemondream: {
        number: 25
    },
    greenbell: {
        number: 3
    },
    jalapeno: {
        number: 1
    }
};

// Global middleware
app.use((req, res, next) => {
    console.log(`${req.method} Request Received`);
    next();
});

// Route specific middleware
app.use('/peppers/:pepperName', (req, res, next) => {
    const pepperName = req.params.pepperName;
    if (!bagOfPeppers[pepperName]) {
        console.log('Response Sent');
        return res.status(404).send('Pepper with that name does not exist');
    }
    req.pepper = bagOfPeppers[pepperName];
    req.pepperName = pepperName;
    next();
});

// GET http://localhost:4001/peppers/
app.get('/peppers/', (req, res, next) => {
    res.send(bagOfPeppers);
    console.log(bagOfPeppers);
});

// POST http://localhost:4001/peppers/
app.post('/peppers/', (req, res, next) => {
    let bodyData = '';
    req.on('data', (data) => {
        bodyData += data;
    });

    req.on('end', () => {
        const body = JSON.parse(bodyData);
        const pepperName = body.name;
        if (bagOfPeppers[pepperName] || bagOfPeppers[pepperName] === 0) {
            return res.status(400).send('Pepper with that name already exists!');
        }
        const numberOfPeppers = Number(body.number) || 0;
        bagOfPeppers[pepperName] = {
            number: numberOfPeppers
        };
        res.send(bagOfPeppers);
        console.log(bagOfPeppers);
    });
});

// GET http://localhost:4001/peppers/yummy
app.get('/peppers/:pepperName', (req, res, next) => {
    res.send(req.pepper);
    console.log(bagOfPeppers);
});

// POST http://localhost:4001/peppers/yummy/add
app.post('/peppers/:pepperName/add', (req, res, next) => {
    let bodyData = '';
    req.on('data', (data) => {
        bodyData += data;
    });

    req.on('end', () => {
        const numberOfPeppers = Number(JSON.parse(bodyData).number) || 0;
        req.pepper.number += numberOfPeppers;
        res.send(req.pepper);
        console.log(bagOfPeppers);
    });
});

// POST http://localhost:4001/peppers/reaper/remove
app.post('/peppers/:pepperName/remove', (req, res, next) => {
    let bodyData = '';
    req.on('data', (data) => {
        bodyData += data;
    });

    req.on('end', () => {
        const numberOfPeppers = Number(JSON.parse(bodyData).number) || 0;
        if (req.pepper.number < numberOfPeppers) {
            return res.status(400).send('Not enough peppers in the jar to remove!');
        }
        req.pepper.number -= numberOfPeppers;
        res.send(req.pepper);
        console.log(bagOfPeppers);
    });
});

// DELETE http://localhost:4001/peppers/reaper
app.delete('/peppers/:pepperName', (req, res, next) => {
    req.pepper = null;
    res.status(204).send();
    console.log(bagOfPeppers);
});

// PUT http://localhost:4001/peppers/jalapeno/orange
app.put('/peppers/:pepperName/name', (req, res, next) => {
    let bodyData = '';
    req.on('data', (data) => {
        bodyData += data;
    });

    req.on('end', () => {
        const newName = JSON.parse(bodyData).name;
        bagOfPeppers[newName] = bagOfPeppers[req.pepperName];
        bagOfPeppers[req.pepperName] = null;
        res.send(bagOfPeppers[newName]);
        console.log(bagOfPeppers);
    });
});

app.listen(PORT, () => {
    console.log(`Server is listening on port ${PORT}`);
});

Array Of Paths In Express Middleware

Another interesting thing that you can do with specifying which path a middleware will execute on is to provide an array of paths as the first argument to the app.use() function. In the simple example below, we have three Express routes. With the supplied middleware, a message will be logged to the console when either the first or second route is visited. When the third route is visited, no logging happens.

const express = require('express');
const app = express();
const PORT = process.env.PORT || 4001;
app.listen(PORT, () => {
    console.log(`Server is listening on port ${PORT}`);
});

app.use(['/one', '/two'], (req, res, next) => {
    console.log('triggers on /one and /two');
    next();
});

app.get('/one', (req, res, next) => {
    res.send('visiting /one');
});

app.get('/two', (req, res, next) => {
    res.send('visiting /two');
});

app.get('/three', (req, res, next) => {
    res.send('visiting /three');
});

More Than One Callback In Middleware

Middleware in Express is not limited to just one callback function at a time. You can also pass two or more callbacks when using middleware to get the desired result. For example, consider this code below that makes use of two middlewares at a time during request processing.

app.get('/peppers', middleware1, middleware2, getPeppers);

function middleware1(req, res, next) {
    // perform first middleware function
    next();  // move on to the next middleware
    next(err);  // or trigger error handler
}

function middleware2(req, res, next) {
    // perform second middleware function
    next();  // move on to the next middleware
    next(err);  // or trigger error handler
}

Using Pre Built Middlware

The beautiful thing about open source software is that there is likely a solution for almost any problem you need to solve during your coding activities. Middleware in Express is no different. There are many available pre-built middlewares that you can simply require into your application and make great use of them right away.

For example, if we needed to write a web server from the ground up each time we wanted to build a website, there would be a lot of waste in solving problems that have already been solved for us by the larger community. So if you need to build a web server in JavaScript, then Express already has you covered.

A common package to use as middleware in Express is morgan. Morgan is a Node.js and Express middleware to log HTTP requests and errors which makes logging in your Express application really easy. In all of the code we’ve written so far, there are many manual calls to console.log(), and this can make the code a bit more ugly and error-prone. Why spend time spent thinking about and writing code that accomplishes common tasks when there is software that exists already to do it for you?

This is where you would use something like Morgan. Here we include it in the example Pepper application above by using the following code:

const morgan = require('morgan');
app.use(morgan('combined'));

When we run all the tests and check the output in the terminal, we can now see what type of request was made, to which path it was made, and the time it took to process the request.

GET /peppers/ 200 2.783 ms - 147
POST /peppers/ 200 1.469 ms - 148
GET /peppers/yummy 200 0.982 ms - 152
POST /peppers/yummy/add 200 0.623 ms - 157
POST /peppers/reaper/remove 200 0.755 ms - 161
DELETE /peppers/reaper 200 0.784 ms - 156
PUT /peppers/jalapeno/name 200 0.310 ms - 160

We used the combined logging format but it is also useful to know that Morgan has the following log levels available.

  • combined‘: which gives you the Apache standard combined format for your logs.
  • common‘: referencing the Apache standard common format.
  • dev‘: A color-coded (by request status) log format.
  • short‘: Shorter than the default format, including just the few items you’d expect a request logline would have.
  • tiny‘: Even shorter, just the response time and a few extra items.

This table shows some of the most commonly used middleware in Express.

body-parser Parse HTTP request body.
compression Compress HTTP responses.
connect-rid Generate unique request ID.
cookie-parser Parse cookie header and populate req.cookies.
cookie-session Establish cookie-based sessions.
cors Enable cross-origin resource sharing (CORS) with various options.
csurf Protect from CSRF exploits.
errorhandler Development error-handling/debugging.
method-override Override HTTP methods using header.
morgan HTTP request logger.
multer Handle multi-part form data.
response-time Record HTTP response time.
serve-favicon Serve a favicon.
serve-index Serve directory listing for a given path.
serve-static Serve static files.
session Establish server-based sessions (development only).
timeout Set a timeout period for HTTP request processing.
vhost Create virtual domains.

How To Use Express Middleware Summary

Middleware in Express can help us to write cleaner and more maintainable code. We’ve seen how middleware makes it possible to provide functionality in many places without duplicating code. In Express we can serve data by route, with each possible endpoint being treated as a unique endpoint of our application. We saw that we can link these middlewares using next() to continue to the next middleware in the stack. In addition, we learned that relying on external, open-source middleware lets us harness the power of the Express web server, the Node environment, and the JavaScript programming language.