Express Route Middleware
Middleware functions are a fundamental part of Express.js applications. They are functions that have access to the request and response objects, and they can execute code, modify request and response objects, end the request-response cycle, or call the next middleware in the stack.
Introduction to Route Middleware
In Express.js, middleware functions are like checkpoints that a request passes through before getting a response. They can perform various tasks such as:
- Authentication and authorization
- Logging request details
- Parsing request bodies
- Handling errors
- Modifying the request or response objects
Middleware can be applied at different levels:
- Application-level middleware (applied to all routes)
- Router-level middleware (applied to specific router instances)
- Route-level middleware (applied to specific routes)
- Error-handling middleware
In this tutorial, we'll focus on route middleware and how to use it effectively in your Express applications.
Basic Middleware Syntax
A middleware function takes three arguments:
req
: The request objectres
: The response objectnext
: A function that passes control to the next middleware
Here's the basic structure:
function myMiddleware(req, res, next) {
// Do something with req and res
// Call next() to pass control to the next middleware function
next();
}
Creating and Using Route Middleware
Let's create a simple middleware function that logs request information:
const express = require('express');
const app = express();
// Middleware function
const requestLogger = (req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} to ${req.url}`);
next(); // Don't forget to call next!
};
// Apply middleware to a specific route
app.get('/hello', requestLogger, (req, res) => {
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
When a user makes a GET request to /hello
, the console will show something like:
[2023-10-14T15:30:45.123Z] GET to /hello
Multiple Middleware Functions
You can apply multiple middleware functions to a route by passing them as additional arguments:
function validateUser(req, res, next) {
// Check if user is authenticated
if (req.query.userId) {
next(); // User provided, continue to the next middleware
} else {
res.status(401).send('Unauthorized: User ID required');
// Note: we don't call next() here because we're ending the request
}
}
function logUserActivity(req, res, next) {
console.log(`User ${req.query.userId} is accessing the system`);
next();
}
// Apply both middleware functions to a route
app.get('/dashboard', validateUser, logUserActivity, (req, res) => {
res.send('Welcome to your dashboard');
});
Using Router-Level Middleware
When you have multiple routes that need the same middleware, you can apply middleware at the router level:
const express = require('express');
const router = express.Router();
// Apply middleware to all routes in this router
router.use((req, res, next) => {
console.log('API request received at:', Date.now());
next();
});
// Define routes
router.get('/users', (req, res) => {
res.send('User list');
});
router.get('/products', (req, res) => {
res.send('Product list');
});
// Use the router in the main app
const app = express();
app.use('/api', router);
app.listen(3000);
Now any request to /api/users
or /api/products
will trigger the logging middleware.
Common Middleware Use Cases
Let's explore some common practical uses for middleware in Express applications.
Authentication Middleware
function authenticateUser(req, res, next) {
const authToken = req.headers.authorization;
if (!authToken || !authToken.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Authentication required' });
}
const token = authToken.split(' ')[1];
// In a real app, you would verify the token
// For this example, we'll just check if it's the string "valid-token"
if (token === 'valid-token') {
req.user = { id: 123, name: 'Example User' };
next();
} else {
res.status(401).json({ error: 'Invalid authentication token' });
}
}
app.get('/protected', authenticateUser, (req, res) => {
res.json({
message: 'Protected data',
user: req.user
});
});
Request Validation Middleware
function validateProductData(req, res, next) {
const { name, price } = req.body;
const errors = [];
if (!name || name.length < 3) {
errors.push('Name must be at least 3 characters long');
}
if (!price || isNaN(price) || price <= 0) {
errors.push('Price must be a positive number');
}
if (errors.length > 0) {
return res.status(400).json({ errors });
}
next();
}
app.post('/products', validateProductData, (req, res) => {
// At this point, we know the data is valid
res.json({ message: 'Product created successfully' });
});
Error Handling Middleware
Error handling middleware has a special signature with four arguments instead of three:
const express = require('express');
const app = express();
app.get('/divide/:a/:b', (req, res, next) => {
const a = parseInt(req.params.a);
const b = parseInt(req.params.b);
if (b === 0) {
// Create an error and pass it to the next middleware
const error = new Error('Cannot divide by zero');
error.statusCode = 400;
return next(error);
}
res.json({ result: a / b });
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error('Error:', err.message);
res.status(err.statusCode || 500).json({
error: err.message || 'Internal Server Error'
});
});
app.listen(3000);
Creating Reusable Middleware Modules
As your application grows, you'll want to organize middleware into separate files:
// middleware/logger.js
module.exports = function logger(req, res, next) {
console.log(`${req.method} ${req.url}`);
next();
};
// middleware/auth.js
module.exports = function auth(req, res, next) {
if (req.session && req.session.user) {
return next();
}
res.redirect('/login');
};
Then import and use them in your main app file:
const express = require('express');
const logger = require('./middleware/logger');
const auth = require('./middleware/auth');
const app = express();
// Apply logger to all routes
app.use(logger);
// Apply auth to specific routes
app.get('/admin', auth, (req, res) => {
res.send('Admin Dashboard');
});
app.listen(3000);
Third-party Middleware
Express.js has a vast ecosystem of third-party middleware. Here are some popular ones:
cors
: Enable Cross-Origin Resource Sharing (CORS)morgan
: HTTP request loggerbody-parser
: Parse request bodiesexpress-session
: Session managementhelmet
: Security headers
Here's how to use them:
const express = require('express');
const cors = require('cors');
const morgan = require('morgan');
const bodyParser = require('body-parser');
const session = require('express-session');
const helmet = require('helmet');
const app = express();
// Apply third-party middleware
app.use(helmet()); // Add security headers
app.use(cors()); // Enable CORS for all routes
app.use(morgan('dev')); // Log requests to console
app.use(bodyParser.json()); // Parse JSON request bodies
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: false
})); // Enable sessions
// Your routes here
app.get('/', (req, res) => {
res.send('Hello World');
});
app.listen(3000);
Middleware Execution Flow
Understanding the order of middleware execution is crucial:
const express = require('express');
const app = express();
app.use((req, res, next) => {
console.log('Middleware 1 - Start');
next();
console.log('Middleware 1 - End');
});
app.use((req, res, next) => {
console.log('Middleware 2 - Start');
next();
console.log('Middleware 2 - End');
});
app.get('/', (req, res) => {
console.log('Route handler');
res.send('Hello World');
});
app.listen(3000);
When you visit the root route, the console output will be:
Middleware 1 - Start
Middleware 2 - Start
Route handler
Middleware 2 - End
Middleware 1 - End
This demonstrates the "onion" structure of middleware — the request passes through layers on the way in, and the response passes back through them on the way out.
Summary
Route middleware is a powerful feature of Express.js that allows you to:
- Process requests before they reach the route handlers
- Modify request and response objects
- End the request-response cycle early if needed
- Pass control to the next middleware function
- Handle errors in a centralized way
By mastering middleware, you can build modular, maintainable Express applications that follow the separation of concerns principle.
Additional Resources
Exercises
- Create a middleware that tracks the response time of each request and logs it.
- Build a rate-limiting middleware that allows only 5 requests per minute from the same IP address.
- Write a middleware that checks if a user has the appropriate role to access specific routes.
- Create error-handling middleware that formats errors differently based on whether the app is in development or production mode.
- Build a middleware chain that validates and sanitizes user input for a form submission.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)