Middlewares in Express js

In this blog I talk about middleware, what they are and how to implement them in express js.

What is middleware?

Middlewares are functions, the reason they are called "middleware" is because they are functions that are executed after a request is made to the server and a response is sent back.

Hence any function that executes after a request is made to the server and a response is sent back is called middleware. This would include functions that fetch data from a database or do any kind of manipulation and filtering or it could be some custom logic as well.

What makes middleware different from regular functions is also the fact that middlewares have access to the request and response object and can manipulate the request and response object as well. They also have access to a next() function which we'll see later.

Let's understand this better by writing some middlewares in express js.

Writing a basic middleware function

app.get("/home", (req, res) => {
  console.log("This is a middleware");
  //sending back a response
  res.send("Home route");
});

In the above example, we have a single middleware function that prints "This is a middleware". The above function executes after a request is made to the home route and before a response is sent using res.send().

Now a question that may arise here is why can't we simply write all of our logic inside app.get()? What is the need of creating more middleware functions when everything can be done inside a single one?

The answer to that is the same as why we need functions. To avoid repetition of code, middleware functions are usually created to prevent repetition. If we need to use some functions in multiple endpoints, simply creating functions that can be called again and again keeps the code clean and easy to understand.

Let's look at another example where we have two functions.

function middleware1(req, res, next) {
    console.log("This is middleware1");
    next();
}

app.get("/", middleware1, (req, res) => {
  console.log("This middleware executes after middleware1");
  res.send("Root route");
});
This is middleware1
This middleware executes after middleware1

Here we have two middleware, the first is middleware1 and the second is the middleware inside app.get("/"). You can observe that both middleware have response and request as arguments, but middleware1 also contains a next argument.

How next() works

Usage

next() calls the next middleware function in line to be executed.

function middleware1(req, res, next) {
  console.log("This is middleware1");
  next();   //next calls middleware2
}
function middleware2(req, res, next) {
  console.log("This is middleware2");
  next();   //next here calls the last middleware
}

app.get("/", middleware1, middleware2, (req, res) => {
  console.log("This middleware executes after 1 and 2 in this case");
  res.send("Root route");
});

Here the order of execution is middleware1->middleware2->last middleware. Calling next() calls the next middleware. Let's see what happens if we don't call next() after middleware1.

function middleware1(req, res, next) {
  console.log("This is middleware1");
  res.send(
    "Middleware1 is sending the response."
  );
}
function middleware2(req, res, next) {
  console.log("This is middleware2");
  next();
}

app.get("/", middleware1, middleware2, (req, res) => {
  console.log("third middleware");
  res.send("Root route");
});
This is middleware1

We don't call next() after middleware1 and instead send the response in middleware1 so middleware2 and the last middleware don't get executed.

next() does not act as return or break

A thing to note here is that next() does not act as return or break do, next only calls the next middleware in line, so if there is some logic written below next(), that would be executed after the other middlewares are done executing.

Let's take another example where we use next() before we actually execute the logic in middleware1 .

function middleware1(req, res, next) {
  next(); //here next() is called before console.log() but it doesn't return out of this function
  console.log("This is middleware1");
}
function middleware2(req, res, next) {
  console.log("This is middleware2");
  next();
}

app.get("/", middleware1, middleware2, (req, res) => {
  console.log("third middleware");
  res.send("Root route");
});
This is middleware2
third middleware
This is middleware1

Here next() calls middleware2 before the console.log() can execute but it does not return out of middleware1, the console.log("This is middleware1") just comes at the very end after the other middleware have executed.

This also shows that the middleware functions were executing even after the response was sent. Hence it is important to use next() appropriately.

Global Middleware

Till now we looked at middleware which were specific to a route ("/" or "/home"). What if there was a middleware which we needed across all routes globally?

Express provides a way to use middlewares globally.

app.use(globalmiddleware); //global middleware will now be executed with all routes

function globalmiddleware(req, res, next) {
  console.log("This is a global middleware");
}

Middlewares are executed in order, so in the above case any middleware after globalmiddleware would never execute since we are not calling next(). Hence it is important to use next() here as well if we are writing custom global middleware.

Also note that global middlewares are usually declared at the top to ensure they are called first, the order in which global middlewares are called depends on the position of app.use(). Let's understand with an example.

When declared at the top

app.use(globalmiddleware); //here globalmiddleware always executes first

function globalmiddleware(req, res, next) {
  console.log("This is a global middleware");
  next();
}

function middleware1(req, res, next) {
  console.log("This is middleware1");
  next();
}
function middleware2(req, res, next) {
  console.log("This is middleware2");
  next();
}

app.get("/", middleware1, middleware2, (req, res) => {
  console.log("This is middleware3");
  res.send("Root route");
});
This is a global middleware
This is middleware1
This is middleware2
This is middleware3

Here global middleware is declared at the top so its executed first.

When declared at the end

function globalmiddleware(req, res, next) {
  console.log("This is a global middleware");
  next();
}

function middleware1(req, res, next) {
  console.log("This is middleware1");
  next();
}
function middleware2(req, res, next) {
  console.log("This is middleware2");
  next();
}

app.get("/", middleware1, middleware2, (req, res, next) => {
  console.log("This is middleware3");
  next(); //here next() cause the global middleware since thats next in line
  res.send("Root route");
});
app.use(globalmiddleware); //this will get executed at the end
This is middleware1
This is middleware2
This is middleware3
This is a global middleware

The global middleware is used at the end hence it is executed at the very end after middleware3 calls next().

Third-party middleware

There are many third-party middleware that you can install, import and use globally or in specific routes where you need. Some of these include express router, body-parser, cookie-parser etc. They are used in the same way as the above middlewares we created for our custom use case.

That is all about middlewares in express js. I hope this blog helped you and if it did do let me know !!