Middlewares in Express along with Request Response Cycle


Reading time: 35 minutes | Coding time: 10 minutes

Before we dive deep into middlewares, It is very important to learn about the request-response cycle in Express to understand the concept of middlewares.
The request-response cycle is the true essence of Express development.

⭐ Request-Response Cycle -


  1. Express app receives a request when someone accesses a server.
  2. For which it creates a request and response object.
  3. The data is then used to generate and send back a meaningful response.
  4. Now, to process that data, in Express we use MIDDLEWARES, which can manipulate the request/response object or execute any other code.

Add-a-subheading--1-

It is k/a middleware because it is executed in between i.e in the middle of receiving a request and sending back a response.

All the middlewares that we use in our app are known as Middleware Stack, and the order in which they are executed is decided by the order they are defined in the code. So a middleware that appears first in the code is executed first.

So, what happens is

  1. The request and response object is created through Express.js
  2. Both of them go through each middleware where they are processed or some other code is executed.
  3. Then at the end of each middleware, a next() function is called. next() is a function, that we have access to in each middleware just like the request or response object.
  4. And when the next() function is called, the next middleware in the middleware stack is executed with the exact req and res object.

Add-a-subheading--2-

And this is how both of the objects go through each middleware step by step.
And we can think of it as a Pipeline where our data goes through and is piped from request to final response.

Our last middleware is usually a route handler, where we do not call the next() function, instead, we send the final response object to the client.

So basically if a middleware does not end the request-response cycle, it should call next() to pass the control to the next middleware. Otherwise, the request is left hanging in the middle.

And like this, we complete the entire Request-Response Cycle.

Middleware functions can perform the following tasks:

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

And now, since we know how Express apps work, it will be really easy to understand the middlewares further.

⭐ Types of middlewares -


  1. Application level middleware
  2. Router-level middleware
  3. Error handling middleware
  4. Built in middleware
  5. Third Party middleware

📝 1. Application level middleware -


  • We use app.use() or app.METHOD() to bind the app instance to the application middleware.
  • For instance, in the following code, we have created a middleware that serves no particular URL. The middleware runs for every request.
var app = express()

app.use(function (req, res, next) {
  console.log('Hello from the middleware !')
  next()
})
  • And in the following code, this middleware handles request for the route /data
app.use('/data', function (req, res, next) {
  console.log('This middleware handles the data route')
  next()
})

We can use such middlewares for authentication or logging purposes.

📝 2. Router level middleware


  • Router level middlewares are similar to application-level middlewares except that they are bound to an instance of express.Router().
  • For instance -
var app = express()
var router = express.Router();
router.use(function (req, res, next) {
  console.log('This is a Router middleware');
  next();
})

The above middleware executes for every request to the router.

📝 3. Error handling middlewares -


  • Express identifies a middleware as the error handling middleware when it takes 4 parameters- err,req,res,next
  • They work exactly as other routers do, but with 4 parameters - err,req, res, next.
  • For instance,
app.use(function (err, req, res, next) {
  console.error("Error found !");
  res.status(500).send('Something very wrong happened !')
})
  • To call an error-handling middleware, we need to send the error object in the next() method -

For instance,

app.use('/data', function (req, res, next) {
try
{
console.log('This middleware handles the data route');
}
 catch(err) 
 {
 next(err);
})

Here, if the flow goes to catch block, Express will send the control to the error handling middleware since, next(parameter) specifies that we need an error handler to be called.

📝 4. Built-in middlewares


Express also has some built-in middlewares such as -

  1. express.static() - serves static assets such as images, HTML files etc.
  2. express.json() - parses the incoming request object with JSON payloads.
  3. express.urlencoded() - parses incoming requests with URL-encoded payloads.

To use a built-in middleware, we can simply use app.use().
For instance,

app.use(express.json());

📝 5. Third party middleware -


  • We may use several third party middlewares to add functionality to our Express Apps
  • To use third party modules, we need to install them first and then include it in our app.

For instance,
morgan is a very popular logging middleware. It lets us see the requested data right in the console. To use it, install it first -

npm install morgan

To include it in our code, use

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

And so every time you make a request, morgan displays the request information on the console just like this -

Capture-23

⭐ Using middlewares :


Now that we know about all kinds of middlewares, let's use some of them in our code.

Syntax :

app.use((req,res,next)=>
{
<code goes here>
});

To create a middleware, we use the method app.use, that takes in 3 parameters - req, res and next function.

To get started, download the starter code from here .

The app.js file consists of basic routing done through Express. To learn more about it, you may refer to this article.

The app.js looks like this as of now -

// Require express

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

app.use(express.json());

// Listening to server
const port = 3000;
app.listen(3000, () => {
  console.log(`App running on port ${port}`);
});

// Routes

app.post("/data", (req, res) => {
  console.log(req.body);
  res.status(200).json({ status: "Success !", data: { body: req.body } });
});
app.get("/", (req, res) =>
  res
    .status(200)
    .json({ message: "Hello from the server !", app: "Express-Routes" })
);
app.get("*", (req, res) =>
  res
    .status(404)
    .json({ message: "Route does not exist", app: "Express-Routes" })
);

Now, add the following code after the line app.use(express.json()); .

Note : express.json() is also a built-in middleware used to parse the body from the request object.

app.use((req, res, next) => {
  console.log("Hello from the middleware !");
  next();
});

Now, save the file, run nodemon app.js, and go to 127.0.0.1:3000 in the browser or Postman. As soon as you hit the server, you will find similar results in the console -

mid-1

Our route handler functions, are middlewares themselves because they simply apply to specific URL/route. Our middleware responds to all the requests because it is defined before all the routes.

⭐ Changing order of middlewares :


Let us add one more middleware after the get route on the route / in our app.js. Now, how do you think the app should behave?

app.use((req, res, next) => {
  console.log("Hello from the middleware defined after the route !");
  next();
});

Include the above code, save the file and restart the server. And we observe that we still get the same response -

mid-1

This is because our route handler functions are ending the request-response cycle, and hence any middleware defined after it is never called.
And this is why the order of middlewares matter in our code.

Note : But if we enter a route that does not matches any of the specified routes, say 127.0.0.1:3000/some-random-route, the control passes to the middleware that we just defined because the request-response cycle does not end.

gg
You will notice that in the terminal, we get the following response.

Capture-24

In case you missed anything, you may refer the code here
And we're all done with learning about middlewares ! 🎉

References/Further Reading -


  1. https://expressjs.com/en/guide/writing-middleware.html
  2. https://expressjs.com/en/guide/using-middleware.html