Middleware in Express

Middleware in Express

by Kenny Porterfield | Fri, Jun 25 2021

Category: JavaScript

Middleware

When we receive a request on the server, it goes through a pipeline called the request processing pipeline.

 

In this pipeline, the server receives the request, which is handled by a series of middleware functions. These functions either terminate the response→request cycle by sending a response to the client, or pass control of the cycle to another middleware function.

 

Middleware functions can perform the following tasks (per the Express docs):

  • Execute any code.
  • Make changes to the request and the response objects. End the request-response cycle.
  • Call the next middleware in the stack.

In Express, you call the use() method on the app object to install a middleware function in the request processing pipeline. There are builtin middleware functions in Express we can use directly on the app, you can use third party middleware functions, and you can write your own custom middleware functions.

 

A middleware function takes the response , request , and next arguments. next is always the last argument passed to the function, and it is called in the middleware to pass control of the cycle to the next piece of middleware. The next() function is not a part of the Node.js or Express API, and it could be named anything, but by convention it is always named “next”. To avoid confusion, use this convention.

 

Technically every route handler function in Express is a middleware function, because it takes a request and sends a response to the client, terminating the request→response cycle.

 

Custom Middleware

We'll start out simple, say this middleware function is for logging something every time we make a request. So we perform our logging and call next() to pass control to the next middleware function in the pipeline. When you use a third party or builtin middleware, this is probably taken care of for you, but when writing your own middleware you have to remember. If you don't call next(), the request will be left hanging.

 

The order of middleware loading is important: middleware functions that are loaded first are also executed first. If the logging middleware is loaded after the route handler below, the app would not print “Logging...” because the route handler would terminate the request→response cycle before it gets there.

 

const express = require('express')

const app = express()

app.use((req, res, next) => {
  console.log('Logging...');
  next();
});

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

app.listen(3000);

 

From a clean coding perspective, we don't want to write that whole function within the use() method. It's better to put that in a different module and export it into your express app.

 

logger.js

const logger = (req, res, next) => {
    console.log("Logging...");
    next();
};

module.exports = logger;

 

index.js

const logger = require('./logger');
const express = require('express');
const app = express();

app.use(logger);

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

app.listen(3000)

 

Builtin Middlewares

There are only 3 builtin middlewares in Express: json(), urlencoded(), and static().

 

app.use(express.json());

express.json() returns a builtin middleware function in Express. When we call it in the use() method on the app, it returns a function that reads the request, and if there's a JSON object in the body of the request, it will parse the body of the request into a JSON object and will set the req.body property.

 

app.use(express.urlencoded({ extended: true }));

express.urlencoded() is a builtin middleware that parses incoming requests with URL encoded payloads and populates the req.body property with a JSON object. This is less common in modern applications, but good to know. You should provide an object with an extended property set to true or false. The extended syntax allows for rich objects and arrays to be encoded into the URL-encoded format, allowing for a JSON like experience with URL encoded payloads.

 

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

The last builtin middleware function we have in Express is static() . We use that if we want to serve static files. The argument specifies the directory from which to serve static assets. In this example, we have a public directory that holds any static asset we want to serve up. The function determines the file to serve by combining req.url with the provided directory. When a file is not found, instead of sending a 404 response, it calls next() to move on to the next middleware, allowing for stacking and fall-backs.

 

index.js
 
const logger = require('./logger');
const express = require('express');
const app = express();

app.use(logger);
 
app.use(express.json());
app.use(express.urlencoded({ extended: true }))
app.use(express.static('./public'));

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

app.listen(3000)

 

Third Party Middleware

A list of third party middlewares, some of which are maintained by the Expressjs team, can be found on the Express website at https://expressjs.com/en/resources/middleware.html. You can simply install these via npm and import them into your Express app.

 

It helps to look at the documentation to determine if there's a middleware already out there that can help with what you're trying to do. Out of this list, one that is considered a best practice is helmet, because it helps secure your application by setting various HTTP headers.

 

Another helpful one is morgan, which helps log HTTP requests to the console. You can look at the documentation on its npm page for more details on how it works, what the different options are, and what you can pass to it, but we'll start with a simplest format by passing it 'tiny' which logs the results of a request to the console in the following format:

 

:method :url :status :res[content-length] - :response-time ms

 

index.js

const logger = require('./logger');
const express = require('express');

const app = express();

app.use(logger);
app.use(express.json());
app.use(express.urlencoded({ extended: true }))
app.use(express.static('./public'));
app.use(helmet());
app.use(morgan('tiny'));

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

app.listen(3000);

 

Middleware Performance Considerations

Whatever middleware you add affects your request processing pipeline, so it can slow things down. For that reason, some middlewares may be helpful, like morgan, but you only want to run in development mode and leave out of production for performance reasons. A convenient way to do this is by taking advantage of Node environment variables.

 

The process object is a global object in Node that gives us access to the current process. There is a env property on this object that returns an object containing the user environment. Here we can set a property called NODE_ENV to be our environment variable. By default, this value will be undefined. We can set it by going to our project in the terminal and running the command (on Mac or Linux):

 

export NODE_ENV=development

 

Or:

 

export NODE_ENV=production

 

There are a couple of ways we can access this environment variable to determine whether we are in production or development mode, and then use that to determine which middleware functions we want to use. One is by accessing it from the Node global process object:

 

const environment = process.env.NODE_ENV;

 

Another way we can access it is through Express get method on the app object:

 

const environment = app.get('env');

 

Both of these will return the value of the NODE_ENV variable. One key difference, however, is that by using the Express get() method, if the property is not set, it will set it to 'development' by default. When you use the Node process object, if the property is not set, it will return 'undefined'.

 

Let's say morgan is something we are only interested in using in development, in order to not slow down the request processing pipeline in production. We can do that by simply using an if statement.

 

if (app.get('env') === 'development') {
  app.use(morgan('tiny'));
}

 

Wrapping Up

That's where I'll wrap it up for this post. To recap, now we know what middleware functions are in Express, how they are structured and how we can write our own, what the builtin middleware functions are, where to go to look for third party middleware functions, as well as took a look at some performance implications and how to manage those. Not bad.



Check out other JavaScript blogs.

Comments

Login or create an account to leave a comment.

Recent Blog Posts

Get in touch

Kenny Porterfield
kjport3@gmail.com
LinkedIn

Or, send me a message from here: