What is Middleware?

Middleware is a cornerstone concept in Express.js, essential for handling requests and responses in your application. Understanding middleware thoroughly can significantly enhance your ability to build scalable and maintainable web applications. This blog post will take you on a detailed journey through what middleware is, how it works, the different types of middleware, and how to create and use middleware effectively in Express.js.

What is Middleware?

Middleware functions are essentially functions that have access to the request object (req), the response object (res), and the next middleware function in the request-response cycle. They are used to execute any code, modify the request and response objects, end the request-response cycle, and call the next middleware function in the stack.

The middleware function signature in Express.js looks like this:

function middlewareFunction(req, res, next) {
  // Middleware logic
  next(); // Call the next middleware in the stack
}

Key Components

  • req: The request object, representing the HTTP request, has properties for the request query string, parameters, body, HTTP headers, etc.

  • res: The response object, representing the HTTP response that an Express app sends when it gets an HTTP request.

  • next: A function that, when called, passes control to the next middleware function in the stack.

How Middleware Works

When a request is made to an Express.js application, the middleware functions are executed sequentially in the order they are added. Each middleware function can either:

  1. Pass Control to the Next Middleware: By calling next(), the middleware function passes control to the next function in the stack.

  2. End the Request-Response Cycle: By sending a response (e.g., res.send(), res.json(), res.status().send()), the middleware function ends the request-response cycle, and no further middleware functions are executed.

Example of a Simple Middleware Chain

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

// Middleware 1
app.use((req, res, next) => {
  console.log('First middleware');
  next(); // Passes control to the next middleware
});

// Middleware 2
app.use((req, res, next) => {
  console.log('Second middleware');
  next(); // Passes control to the next middleware
});

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

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

In this example, when a request is made to the root route (/), the first middleware logs a message and calls next(), which passes control to the second middleware. The second middleware logs another message and calls next(), which then passes control to the route handler that sends the response.

Types of Middleware

Middleware in Express.js can be categorized into several types based on their usage and scope:

1. Application-Level Middleware

Application-level middleware is bound to an instance of express using app.use() or app.METHOD() (where METHOD is an HTTP method like GET, POST, PUT, etc.).

Example:

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

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

app.get('/', (req, res) => {
  res.send('Hello from application-level middleware!');
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

2. Router-Level Middleware

Router-level middleware works in the same way as application-level middleware except it is bound to an instance of express.Router().

Example:

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

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

router.get('/', (req, res) => {
  res.send('Hello from router-level middleware!');
});

const app = express();
app.use('/router', router);

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

3. Error-Handling Middleware

Error-handling middleware functions have four arguments: err, req, res, and next. These functions are used to catch and handle errors.

Example:

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

app.use((req, res, next) => {
  next(new Error('Something went wrong!')); // Pass an error to the next middleware
});

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

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

4. Built-in Middleware

Express.js comes with several built-in middleware functions, such as express.static to serve static files, express.json to parse JSON payloads, and express.urlencoded to parse URL-encoded payloads.

Example:

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

app.use(express.static('public')); // Serve static files from the 'public' directory
app.use(express.json()); // Parse JSON bodies
app.use(express.urlencoded({ extended: true })); // Parse URL-encoded bodies

app.post('/submit', (req, res) => {
  res.send(`Received data: ${JSON.stringify(req.body)}`);
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

5. Third-Party Middleware

Third-party middleware functions are provided by the community and can be installed via npm. Examples include morgan for logging, body-parser for parsing request bodies, and cors for enabling Cross-Origin Resource Sharing.

Example:

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

app.use(morgan('combined')); // Use morgan for logging

app.get('/', (req, res) => {
  res.send('Hello from third-party middleware!');
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

Creating Custom Middleware

Creating custom middleware in Express.js is straightforward. You define a function with the signature (req, res, next) and implement the desired logic.

Example: Logging Middleware

This middleware logs the HTTP method and URL of each incoming request.

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

function logRequest(req, res, next) {
  console.log(`${req.method} ${req.url}`);
  next(); // Pass control to the next middleware function
}

app.use(logRequest);

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

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

Example: Authentication Middleware

This middleware checks for an authorization header and validates a token.

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

function authenticate(req, res, next) {
  const token = req.headers['authorization'];

  if (token === 'my-secret-token') {
    next(); // Token is valid, proceed to the next middleware or route handler
  } else {
    res.status(403).send('Forbidden'); // Token is invalid, end the request-response cycle
  }
}

app.use(authenticate);

app.get('/protected', (req, res) => {
  res.send('This is a protected route');
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

Middleware Order

The order in which middleware functions are added is crucial because they are executed sequentially. Middleware should be added before any route handlers that need to use that middleware.

Example of Middleware Order

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

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

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

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

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

In this example, Middleware 1 will run before Middleware 2 for every incoming request, and both will run before the route handler.

Conclusion

Middleware is a fundamental concept in Express.js that provides a powerful and flexible way to handle the request-response cycle. By understanding how middleware works and learning to create and use different types of middleware, you can build modular, reusable, and maintainable web applications.

Experiment with different middleware types, chain them together to create complex request handling pipelines, and leverage the power of both built-in and third-party middleware to enhance your Express.js applications. Mastering middleware will significantly improve your ability to develop efficient and scalable web applications. Happy coding!

Last updated