Middleware functions are the backbone of Express applications, handling everything from request processing to authentication. This guide will transform you into a middleware expert.
1. What is Middleware?
Key Characteristics
- Functions that access
req
andres
objects - Can execute any code
- Can modify request/response objects
- Can end the request-response cycle
- Can call the next middleware (
next()
)
Basic Structure
function myMiddleware(req, res, next) {
// Do something with req/res
next(); // Pass control to next middleware
}
// Usage
app.use(myMiddleware);
2. Types of Middleware
1. Application-Level Middleware
// Runs for every request
app.use((req, res, next) => {
console.log(`Request received: ${req.method} ${req.path}`);
next();
});
// Specific path
app.use('/admin', (req, res, next) => {
console.log('Admin area access');
next();
});
2. Router-Level Middleware
const router = express.Router();
router.use((req, res, next) => {
console.log('Router middleware activated');
next();
});
3. Error-Handling Middleware
// Note the 4 parameters
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
4. Built-In Middleware
app.use(express.json()); // Parse JSON bodies
app.use(express.urlencoded({ extended: true })); // Parse form data
app.use(express.static('public')); // Serve static files
5. Third-Party Middleware
const helmet = require('helmet');
const cors = require('cors');
app.use(helmet()); // Security headers
app.use(cors()); // Enable CORS
3. Middleware Execution Flow
Understanding the order is crucial:
- Middleware executes in the order they’re defined
- Request flows through middleware until response is sent
- Error handlers only trigger when
next(err)
is called - Route handlers are essentially terminal middleware
4. Practical Middleware Examples
Request Logging
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.url} - ${res.statusCode} (${duration}ms)`);
});
next();
});
Authentication
function authenticate(req, res, next) {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}
try {
const user = verifyToken(token);
req.user = user; // Attach user to request
next();
} catch (err) {
next(err); // Pass to error handler
}
}
// Protected route
app.get('/profile', authenticate, (req, res) => {
res.json({ user: req.user });
});
5. Advanced Middleware Patterns
Conditional Middleware
function conditionalMiddleware(condition) {
return (req, res, next) => {
if (condition(req)) {
// Apply middleware logic
next();
} else {
next('route'); // Skip to next route
}
};
}
app.use(conditionalMiddleware(req => req.headers['x-special-header']));
Middleware Configuration
function configurableMiddleware(options = {}) {
return (req, res, next) => {
// Use options to customize behavior
if (options.log) {
console.log(`${req.method} ${req.url}`);
}
next();
};
}
app.use(configurableMiddleware({ log: true }));
Async Middleware
const asyncMiddleware = fn =>
(req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch(next);
};
app.use(asyncMiddleware(async (req, res, next) => {
const data = await fetchData();
req.data = data;
next();
}));
6. Common Middleware Pitfalls
- Forgetting to call next() – Causes hanging requests
- Error handling order – Error middleware must come last
- Modifying req/res too late – Headers already sent
- Blocking operations – Defeats async benefits
- Too many middleware – Impacts performance
7. Essential Third-Party Middleware
Middleware | Purpose | Installation |
---|---|---|
helmet | Security headers | npm install helmet |
cors | Cross-Origin Resource Sharing | npm install cors |
morgan | HTTP request logging | npm install morgan |
compression | Response compression | npm install compression |
express-rate-limit | Rate limiting | npm install express-rate-limit |
Next: Working with Databases →
Middleware Best Practices
- Keep middleware focused on single responsibilities
- Document custom middleware behavior
- Place error handlers after other middleware
- Use existing middleware solutions when possible
- Test middleware in isolation