Understanding Middleware in Express.js

Understanding Middleware in Express.js

Middleware is one of the core concepts that makes Express.js such a flexible and powerful framework for building web applications on top of Node.js. Whether you're building a simple API or a complex web application, middleware is a crucial part of the process. In this article, we’ll dive deep into what middleware is, how it works in Express.js, and how you can use it to enhance the functionality of your server.

What is Middleware?

In simple terms, middleware functions are functions that have access to the request (req) and response (res) objects in an Express app. They also have access to the next() function, which allows them to pass control to the next middleware function in the stack. Middleware is used to perform various tasks like modifying request or response data, handling authentication, logging requests, and even managing errors.

In Express.js, middleware functions are executed in the order they are defined, and each function can either end the request-response cycle or pass control to the next middleware by calling next().

Here’s the basic structure of a middleware function in Express:

app.use((req, res, next) => {
    // Your middleware logic here
    next(); // Pass control to the next middleware
});

Types of Middleware in Express.js

Express.js supports several types of middleware:

  1. Application-Level Middleware

  2. Router-Level Middleware

  3. Error-Handling Middleware

  4. Built-in Middleware

  5. Third-Party Middleware

Let’s explore each of these in detail.

1. Application-Level Middleware

Application-level middleware is bound to an instance of the Express app object. You can apply it to all routes, or to specific routes, depending on your needs.

Example of applying middleware to all routes:

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

app.use((req, res, next) => {
    console.log('Request received at ' + new Date().toISOString());
    next();
});

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

In the above example, the middleware logs the time of each incoming request and passes control to the next middleware or route handler by calling next().

You can also apply middleware to specific routes:

app.use('/user/:id', (req, res, next) => {
    console.log('Request Type:', req.method);
    next();
});

This middleware will only trigger for routes that match /user/:id.

2. Router-Level Middleware

Router-level middleware works similarly to application-level middleware but is applied to an instance of the express.Router object. This is useful when you want to apply middleware to a specific set of routes, such as those in a certain section of your app.

Here’s how you can use router-level middleware:

const express = require('express');
const app = express();
const router = express.Router();

router.use((req, res, next) => {
    console.log('Router-level middleware');
    next();
});

router.get('/users', (req, res) => {
    res.send('User list');
});

app.use('/api', router);

In this case, the router-level middleware will only be executed for routes that begin with /api.

3. Error-Handling Middleware

Error-handling middleware is used to catch and handle errors in your application. In Express, error-handling middleware is defined by having four arguments: err, req, res, and next. It’s important to always define the err parameter to distinguish it as an error handler.

Here’s an example:

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

You can use error-handling middleware to log errors, send custom error responses, or even notify external services like Sentry or Slack.

4. Built-In Middleware

Express provides some built-in middleware functions to make common tasks easier. Here are some examples:

  • express.json(): Parses incoming requests with JSON payloads.

  • express.urlencoded(): Parses incoming requests with URL-encoded payloads.

  • express.static(): Serves static files like images, CSS, and JavaScript.

Example:

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

app.post('/submit', (req, res) => {
    res.send('Data received: ' + JSON.stringify(req.body));
});

In this example, the express.json() middleware allows the server to parse JSON data sent in requests, while express.static() serves static files from the public directory.

5. Third-Party Middleware

There are many third-party middleware packages available for Express.js that can help you add additional functionality to your app. Some popular examples include:

  • morgan: For logging HTTP requests.

  • cors: For enabling Cross-Origin Resource Sharing.

  • cookie-parser: For parsing cookies attached to client requests.

Here’s how you can use third-party middleware like morgan:

const morgan = require('morgan');
app.use(morgan('dev')); // Logs requests in 'dev' format

Custom Middleware

While Express provides many built-in and third-party middleware options, you can also create your own custom middleware to perform specific tasks.

Here’s an example of a simple custom middleware that checks for an API key:

const checkApiKey = (req, res, next) => {
    const apiKey = req.query.api_key;
    if (apiKey && apiKey === '12345') {
        next(); // Proceed if the API key is valid
    } else {
        res.status(401).send('Unauthorized');
    }
};

app.use(checkApiKey);

app.get('/data', (req, res) => {
    res.send('Protected data');
});

In this example, the middleware checks if the correct API key is provided in the query string. If the key is valid, the middleware allows the request to proceed by calling next(). Otherwise, it sends a 401 Unauthorized response.

Order of Middleware Execution

One key thing to understand about middleware in Express is the order in which it is executed. Middleware functions are executed in the order they are defined, so the sequence of app.use() calls matters.

Here’s an example to demonstrate this:

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

app.use((req, res, next) => {
    console.log('Middleware 2');
    res.send('Hello from Middleware 2');
});

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

In this case, “Middleware 1” will be logged first, followed by “Middleware 2.” However, since the response is sent in Middleware 2, Middleware 3 will not execute.

Conclusion

Middleware in Express.js is a powerful tool that allows you to extend the functionality of your server in flexible and reusable ways. Whether you're handling logging, authentication, error management, or custom tasks, middleware lets you keep your code organized and maintainable. By understanding the different types of middleware and how to use them effectively, you can build more robust and scalable web applications with Express.

Did you find this article valuable?

Support Shivam's Blog❤️❤️ by becoming a sponsor. Any amount is appreciated!