The Role of Middleware: Enhancing the Functionality of Your Web Applications

The Role of Middleware: Enhancing the Functionality of Your Web Applications

Middleware is a fairly common term when it comes to software computing. However, in my experience most young developers assume it to be a heavy concept. And so, it's rarely used to its full potential. I will try my best to simplify the idea and explain the what, why and hows of Middleware (in Node Js).

What’s with the name?

Middleware refers to a piece of code that sits between the client and server layers of an application, intercepting requests and responses as they flow through the system.

It's like a filter or a processing unit that intercepts incoming requests before they reach the application and performs certain tasks on them, such as authentication, logging, parsing, or validation. Once the middleware has completed its tasks, it passes the request along to the application or the next middleware in line.

Now, the most important part of Middleware lies in its simplicity. It receives the same Req and Res objects as the other Route Handlers receive. One of the most common Middleware you might have used is Body-Parser. Middlewares alter these objects as needed and then the Route Handlers take over.

Creating a Middleware

To create a middleware in Node.js, you can use the use() method provided by the Express framework. Here's an example of how to create a middleware function that logs incoming requests:

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

// Middleware function to log incoming requests
const myMiddleware= (req, res, next) => {
  console.log(`Incoming request: ${req.method} ${req.url}`);
  next();
};

// Register middleware function with Express app
app.use(myMiddleware);

// Start server
app.listen(3000, () => {
  console.log('Server listening on port 3000');
});

In the above example, the `myMiddleware` function is a middleware function that logs incoming requests to the console. It takes three arguments: req, res, and next. req represents the request object, res represents the response object, and next is a function that is called to pass control to the next middleware function in the chain.

To register the middleware function with the Express app, we use the use() method and pass in the myMiddleware function. This tells Express to use this middleware function for all incoming requests.

There are many different ways to create middleware in Node.js, which we will go over in a bit.

Structure of Middleware

In a typical Express setup, a middleware may look like this:

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something went wrong!');
});

The order in which you apply middleware functions matters. Middleware functions are executed in the order they are added to the middleware stack.The next() is very crucial for the next middleware to be run.

For example:

// Middleware function 1
const middleware1 = (req, res, next) => {
  console.log('Middleware 1');
  next();
};

// Middleware function 2
const middleware2 = (req, res, next) => {
  console.log('Middleware 2');
  next();
};

app.use(middleware1);
app.use(middleware2);

If we run this code the output we’ll get is:

Middleware 1

Middleware 2

However, if next() is not used:

// Middleware function 1
const middleware1 = (req, res, next) => {
  console.log('Middleware 1');
};

// Middleware function 2
const middleware2 = (req, res, next) => {
  console.log('Middleware 2');
  next();
};

// Add middleware to the application
app.use(middleware1);
app.use(middleware2);

On running this code, we get the output:

Middleware 1

How to call Middleware functions?

In Node.js, there are a few different ways of calling middleware depending on the specific use case. Here are some examples:

  1. All routes using app.use(): This method allows you to define middleware that will be called for every route in your application. This is useful for tasks like logging, authentication, and error handling.

     const express = require('express');
     const app = express();
    
     // Middleware function to log all requests
     const logger = (req, res, next) => {
       console.log(`Received ${req.method} request for ${req.url}`);
       next();
     };
     // Call the middleware for all routes
     app.use(logger);
     // Define routes
     app.get('/', (req, res) => {
       res.send('Hello, world!');
     });
     // Start the server
     app.listen(3000, () => {
       console.log('Server listening on port 3000');
     });
    
  2. Specific routes using app.use(): This method allows you to define middleware that will be called for specific routes in your application. This is useful when you need to perform certain tasks only for certain routes.

     const express = require('express');
     const app = express();
    
     // Middleware function to check for admin privileges
     const checkAdmin= (req, res, next) => {
       if (req.user.role !== 'admin') {
         res.status(403).send('Access denied');
       } else {
         next();
       }
     };
     // Call the middleware for a specific route
     app.use('/admin', checkAdmin);
     app.get('/admin', (req, res) => {
       res.send('Welcome to the admin panel!');
     });
     // Start the server
     app.listen(3000, () => {
       console.log('Server listening on port 3000');
     });
    
  3. Inline with route definition: This method allows you to define middleware that will be called for a specific route inline with the route definition. This is useful when you need to perform a task only for a specific route and don't want to define a separate middleware function.

     const express = require('express');
     const app = express();
    
     app.get('/users', (req, res, next) => {
       // Inline middleware to check for query parameter
       if (req.query.search) {
         next();
       } else {
         res.status(400).send('Missing search query parameter');
       }
     }, (req, res) => {
       // Handler function for the route
       const searchQuery = req.query.search;
       res.send(`Search results for "${searchQuery}"`);
     });
     // Start the server
     app.listen(3000, () => {
       console.log('Server listening on port 3000');
     });
    

I hope you learnt some important concepts from this article and now have a better understanding of what Middlewares are.