Express JS middleware chaining
Middleware Chaining in Express.js
Middleware chaining in Express.js refers to the process of executing multiple middleware functions in sequence during the lifecycle of a single HTTP request. This is achieved by calling the next()
function inside each middleware, which passes control to the next middleware in the chain.
Middleware chaining allows you to break down application logic into smaller, modular units that can be executed in order. Each middleware function can either modify the request (req
) and response (res
) objects or terminate the request-response cycle by sending a response.
How Middleware Chaining Works
In Express.js, middleware functions are executed in the order they are declared. When a middleware function completes its task, it calls next()
to pass control to the next middleware. If next()
is not called, the request-response cycle will hang, meaning the request will not proceed to the next middleware or route handler.
Basic Example of Middleware Chaining
const express = require('express');
const app = express();
// First middleware
const middleware1 = (req, res, next) => {
console.log('Middleware 1 executed');
req.customProperty = 'Data from Middleware 1';
next(); // Passes control to the next middleware
};
// Second middleware
const middleware2 = (req, res, next) => {
console.log('Middleware 2 executed');
console.log('Custom Property:', req.customProperty); // Access data from previous middleware
next(); // Passes control to the route handler
};
// Route handler
const finalHandler = (req, res) => {
res.send('Final handler executed');
};
// Use middleware in sequence (chaining)
app.use(middleware1);
app.use(middleware2);
app.get('/', finalHandler);
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Explanation of Example:
middleware1
: Logs a message and adds a custom property to thereq
object.middleware2
: Logs a message, accesses the custom property fromreq
, and passes control to the next step.finalHandler
: The final route handler that responds to the client.
Middleware functions are executed in the declared order:
middleware1
runs first, modifying thereq
object.middleware2
runs next, accessing the modifiedreq
object.- Finally, the route handler runs and sends the response.
Middleware Chaining for Route-Specific Middleware
You can apply middleware chaining directly on a route by listing multiple middleware functions. For example
app.get('/example', middleware1, middleware2, (req, res) => {
res.send('Route handler after middleware chain');
});
Here, when a GET request is made to /example
, the two middleware functions (middleware1
and middleware2
) are executed in order before reaching the route handler.
Example: Middleware Chaining for Authentication and Logging
const isAuthenticated = (req, res, next) => {
if (req.headers.authorization) {
console.log('User authenticated');
next(); // Proceed to the next middleware or route
} else {
res.status(401).send('Unauthorized');
}
};
const logRequest = (req, res, next) => {
console.log(`Request to ${req.url}`);
next(); // Proceed to the next middleware or route
};
// Chain the middleware for the protected route
app.get('/dashboard', isAuthenticated, logRequest, (req, res) => {
res.send('Welcome to the dashboard');
});
In this example:
isAuthenticated
: Checks if the user is authenticated. If they are, it passes control to the next middleware. Otherwise, it terminates the request by sending a 401 Unauthorized response.logRequest
: Logs the request URL, then passes control to the final route handler.- If both middlewares complete successfully, the route handler sends a response.
Error Handling in Middleware Chains
If any middleware in the chain encounters an error, you can pass the error to the next middleware using next(err)
.
const express = require('express');
const app = express();
// Middleware with an error
const middlewareWithError = (req, res, next) => {
const error = new Error('Something went wrong!');
next(error); // Passes the error to the error-handling middleware
};
// Error-handling middleware
const errorHandler = (err, req, res, next) => {
console.error(err.message);
res.status(500).send('Internal Server Error');
};
// Route with middleware chain
app.get('/', middlewareWithError, (req, res) => {
res.send('This will not be reached if there is an error');
});
// Error-handling middleware should be defined after all other middleware
app.use(errorHandler);
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Execution Order of Middleware
- Express executes middleware in the order it is declared.
- Middleware must call
next()
to pass control to the next middleware in the stack. - If a middleware doesn't call
next()
or send a response, the request will hang. - Error-handling middleware must come after all other middleware, and it uses four parameters:
err, req, res, next
.
Practical Use Cases for Middleware Chaining
- Authentication: A middleware to check if the user is logged in, followed by logging requests for auditing.
- Request validation: A middleware to validate incoming data before passing it to the route handler.
- Response modifications: Middleware can modify response headers or content before the final response is sent.
- Error handling: Centralized error-handling middleware that catches errors in previous middleware or route handlers.