Express Middleware Introduction
What is Middleware?
Middleware functions are the backbone of Express.js applications. They are functions that have access to the request object (req
), the response object (res
), and the next middleware function in the application's request-response cycle. These functions can:
- Execute any code
- Modify the request and response objects
- End the request-response cycle
- Call the next middleware in the stack
Think of middleware as a series of processing layers that your requests go through before getting a response. Each layer can do something with the request, then either send a response or pass control to the next middleware.
Basic Middleware Structure
A middleware function follows this basic pattern:
function myMiddleware(req, res, next) {
// Do something with req or res
// Then call next() to pass control to the next middleware
next();
}
The next
parameter is a function that, when called, executes the next middleware in the stack. If you don't call next()
, the request will be left hanging!
How Middleware Works in Express
When a request comes into your Express application, it passes through each middleware function in the order they are defined. This creates a "middleware stack" or pipeline:
Client Request → Middleware 1 → Middleware 2 → Route Handler → Response
Let's see this in action with a simple example:
const express = require('express');
const app = express();
// Middleware 1: Logger
app.use((req, res, next) => {
console.log(`${new Date().toISOString()}: ${req.method} ${req.url}`);
next(); // Pass control to the next middleware
});
// Middleware 2: Request timer
app.use((req, res, next) => {
req.requestTime = Date.now();
next();
});
// Route handler
app.get('/', (req, res) => {
res.send(`Hello! Your request came in at: ${new Date(req.requestTime).toISOString()}`);
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
When a request comes to the root route /
, it will:
- First go through the logger middleware (printing the request info)
- Then through the timer middleware (adding a timestamp to the request)
- Finally reach the route handler (which uses the timestamp to respond)
Adding Middleware to Your Application
There are three ways to use middleware in Express:
1. Application-level middleware
Applied using app.use()
or app.METHOD()
(where METHOD is get, post, etc.):
// This middleware applies to all routes
app.use((req, res, next) => {
console.log('This middleware runs for every request');
next();
});
// This middleware only applies to the specified path
app.use('/user', (req, res, next) => {
console.log('This runs only for routes starting with /user');
next();
});
2. Router-level middleware
Similar to application middleware but bound to an instance of express.Router()
:
const express = require('express');
const router = express.Router();
router.use((req, res, next) => {
console.log('Router specific middleware');
next();
});
router.get('/profile', (req, res) => {
res.send('User profile');
});
// Then use the router in your app
const app = express();
app.use('/user', router);
3. Error-handling middleware
Special middleware that takes four parameters instead of three:
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
Creating Your First Custom Middleware
Let's create a simple authentication middleware:
const express = require('express');
const app = express();
// Authentication middleware
function authMiddleware(req, res, next) {
// Get auth token from header
const authToken = req.headers.authorization;
if (authToken === 'secret-token-123') {
// If authentication is successful
req.user = { id: 1, name: 'Example User' };
next(); // Proceed to the next middleware/route handler
} else {
// If authentication fails
res.status(401).send('Unauthorized: Invalid token');
// Note: We don't call next() here because we're ending the request-response cycle
}
}
// Use the middleware only for protected routes
app.get('/public', (req, res) => {
res.send('This is public content');
});
app.get('/protected', authMiddleware, (req, res) => {
res.send(`Hello, ${req.user.name}! This is protected content.`);
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
Input/Output Example
If you test this with different requests:
Public Route (No Auth Required):
GET /public
Output:
This is public content
Protected Route (Without Auth):
GET /protected
Output:
Unauthorized: Invalid token
Protected Route (With Auth):
GET /protected
Headers: Authorization: secret-token-123
Output:
Hello, Example User! This is protected content.
Practical Use Case: Request Logger
Here's a practical middleware that logs details about incoming requests:
const fs = require('fs');
const path = require('path');
function requestLogger(req, res, next) {
const start = Date.now();
// Process the request
next();
// After the request is processed (this runs after the route handler)
const duration = Date.now() - start;
const log = `${new Date().toISOString()} | ${req.method} ${req.url} | ${res.statusCode} | ${duration}ms\n`;
// Append to log file
fs.appendFile(
path.join(__dirname, 'requests.log'),
log,
(err) => {
if (err) console.error('Failed to log request:', err);
}
);
}
// In your Express app
const app = express();
app.use(requestLogger);
This middleware:
- Records when the request started
- Calls
next()
to process the request - After the route handler completes, calculates the duration
- Logs the request details to a file
Common Built-in and Third-Party Middleware
Express has several built-in middleware functions and there are many popular third-party options:
Built-in middleware:
// Parse JSON bodies
app.use(express.json());
// Parse URL-encoded bodies
app.use(express.urlencoded({ extended: true }));
// Serve static files
app.use(express.static('public'));
Popular third-party middleware:
// Session management
const session = require('express-session');
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: true
}));
// HTTP request logging
const morgan = require('morgan');
app.use(morgan('dev'));
// CORS support
const cors = require('cors');
app.use(cors());
Middleware Execution Order
The order in which you define middleware is crucial. Middleware functions are executed sequentially:
// This runs first
app.use((req, res, next) => {
console.log('Middleware 1');
next();
});
// This runs second
app.use((req, res, next) => {
console.log('Middleware 2');
next();
});
// This route handler runs after both middleware
app.get('/', (req, res) => {
res.send('Hello World!');
});
If a middleware doesn't call next()
, all subsequent middleware and route handlers won't be executed.
Summary
Express middleware is the foundation of Express.js applications. Through middleware:
- You can process requests before they reach route handlers
- You can modify request and response objects
- You can end the request-response cycle or pass control to the next middleware
- You can implement cross-cutting concerns like logging, authentication, and error handling
Understanding middleware is essential for building robust, maintainable Express applications.
Additional Resources
- Express.js Middleware Documentation
- List of Express Middleware
- Writing Middleware (Express.js Guide)
Exercises
- Create a middleware that adds a timestamp to every request object.
- Build a rate-limiting middleware that allows only 10 requests per minute from each IP address.
- Create a middleware chain with at least three custom middleware functions and observe the order of execution.
- Add error-handling middleware to an Express application that catches errors and responds with appropriate status codes.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)