How to write Express.js middleware#

First, let's understand what an Express middleware is.

An Express middleware is a function which is allowed to be a part of Express's request-response cycle. This function can optionally accept an error object, the request object, the response object, and a optional function to invoke the next middleware function in the middleware stack; in that order.

So the sytanx of an Express middleware function can be described thus:

function ([error], request, response[, next]) { ... }

The Express middleware stack be thought of as an array of middleware functions that are executed one after another, with each middleware having the ability to respond to a request.

If a middleware function does not send a response to the request, it must call next() to invoke the next middleware function in the stack, failing to do so will result in the request left hanging, which will enventually lead to the server crashing due to running out of memory.

The middleware parameters are popularly named err, req, res, and next by convention.

Express middleware can be categorized into two types:

  1. Request-handling middleware - omits the error parameter
  2. Error-handling middleware - includes the error parameter

Request-handling middleware#

Request handling middleware have an arity of 3, the parameters are req, res, and next. They are invoked by requests to the server, they may terminate the request-response cycle by sending a response or let the request propagate to the next middleware in the stack by calling next().

A simple request-handling middleware function could look something like this:

function logger(req, res, next) {
  console.log('LOGGED');
  next();
}

All this middleware will do is print "LOGGED" on receiving a request to the server.

We could have a more complex one, an asynchronous middleware which calls next() after fetching data from a database.

async function luckyNumber(req, res, next) {
  var number = await db.getLuckyNumber(); // Make request to the databse to get a lucky number
  req.luckyNumber = number; // Add the lucky number to the request object
  next();
}

Error-handling middleware#

Error-handling middleware have an arity of 4, the parameters are err, req, res, and next. They deal with errors that are generated in the app.

Express has an in-built error handling middleware, which will handle any errors that are generated in the app. So, usually you won't need to write your own error handling middleware.

In case you would like to have some control over how errors are handled, you can create a middleware like this:

app.use(function(err, req, res, next) {
  console.log('ERROR');
  next(err);
});

This error-handling middleware just prints "ERROR" and passes on the error object to the in-built error handler.

In case you would like to have complete control over how errors are handled, you can create a middleware like this:

app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.send('ERROR');
});

This error-handling middleware sets the status code to 500by default and sends "ERROR" as the response string.

Handling errors in middleware#

On encountering any error, middleware functions can either handle it themselves of pass it on to error handling middleware in the stack by calling next(error), where error is the error object.

When any parameter is passed to next(), the error is interpreted as 500 Internal Server Error error, unless otherwise specified.

In the following example, the error is passed in the next() function, which will be caught by the in-built error-handling middleware.

async function luckyNumber(req, res, next) {
  try {
    var number = await db.getLuckyNumber();
    req.luckyNumber = number;
    next();
  } catch (err) {
    next(err);
  }
}

To specify the error type, you can use the http-errors module to create the error object, which will be understood by Express.

In the following example, we send a 503 Service Unavailable error.

var createError = require('http-errors');
...
async function luckyNumber(req, res, next) {
  try {
    var number = await db.getLuckyNumber();
    req.luckyNumber = number;
    next();
  } catch (err) {
    next(createError(503));
  }
}

next is not a reference to the next middleware in the stack, it is a ference to the function which will invoke the next function in the middleware stack.

Organizing middleware functions#

Writing all your middleware functions in the main application file will soon lead to an unsightly, unmanageable messy code. It is important to manage your middleware functions to make them modular and to keep the main application file neat and tidy.

If there are only a few simple middleware functions, it may be OK to keep them in the main application file as shown in the following hypothetical example.

function logger(req, res, next) {
  console.log('LOGGED');
  next();
}

function date(req, res, next) {
  console.log('DATE');
  next();
}

app.get('/example', logger, function (req, res) {
  res.send('EXAMPLE');
});

app.get('/test', logger, date, function (req, res) {
  res.send('TEST');
});

However, in real-world applications middleware functions are not as simple like the example above.

Middleware with any significant amount of code should be implemented as Node.js modules.

Middleware as a Node.js module#

Developing middleware function as Node.js modules help to keep your codebase modular and neat.

Here is an example of a middleware function which is implemented as a Node.js module.

./middleware/example-handler.js
module.exports = function exampleHandler(req, res, next) {
  console.log('LOGGED');
  res.send('EXAMPLE');
}

The module can then be loaded in the application file and specified as the handler function of a route.

./app.js
...
var exampleHandler = require('./middleware/example-handler.js');
app.get('/example', exampleHandler);
...

Configurable middleware#

There will be times when you would like to be able to configure the behavior your middleware. This can be made possible by implementing a module which exports a configurable function and returns the middleware function.

Here is a very simple example of such a middleware.

./middleware/example-handler.js
module.exports = function (enableLog) {
  // Disable log by default
  enableLog = enableLog || false;
  return function exampleHandler(req, res, next) {
    if (enableLog) console.log('LOGGED');
    res.send('EXAMPLE');
  }
}

You then pass the configuration details to the function, which is then applied to the middleware function.

./app.js
...
var exampleHandler = require('./middleware/example-handler.js')(true);
app.get('/example', exampleHandler);
...

Summary#

Express.js middleware are functions that are invoked in response to client request to the server and errors emanating from the application. They are best developed as Node.js modules for the sake of modularity, clarity, and maintainability.

References#

  1. Express.js - Writing middleware for use in Express apps
  2. Express.js - Using middleware
  3. http-errors
  4. Node.js: writing modules