Express.js Deep Dive: Middleware Patterns
Middleware is the backbone of Express. Let's look at patterns that make your APIs more robust and maintainable.
What Is Middleware?
Middleware functions have access to the request, response, and next function. They can:
- Execute code
- Modify request/response
- End the request-response cycle
- Call the next middleware
const myMiddleware = (req, res, next) => {
// Do something
next(); // Pass to next middleware
};
Request Logging
const requestLogger = (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();
};
app.use(requestLogger);
Authentication
const authenticate = async (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
try {
const user = await verifyToken(token);
req.user = user;
next();
} catch (err) {
res.status(401).json({ error: 'Invalid token' });
}
};
// Apply to specific routes
app.get('/api/profile', authenticate, (req, res) => {
res.json(req.user);
});
Error Handling
Error middleware has 4 parameters:
const errorHandler = (err, req, res, next) => {
console.error(err.stack);
if (err.name === 'ValidationError') {
return res.status(400).json({ error: err.message });
}
if (err.name === 'UnauthorizedError') {
return res.status(401).json({ error: 'Unauthorized' });
}
res.status(500).json({ error: 'Internal server error' });
};
// Must be registered last
app.use(errorHandler);
Throw errors from routes:
app.get('/api/users/:id', async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
const err = new Error('User not found');
err.status = 404;
throw err;
}
res.json(user);
} catch (err) {
next(err); // Passes to error handler
}
});
Validation
const validate = (schema) => {
return (req, res, next) => {
const { error } = schema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
next();
};
};
// Usage with Joi
const createUserSchema = Joi.object({
email: Joi.string().email().required(),
password: Joi.string().min(8).required()
});
app.post('/api/users', validate(createUserSchema), createUser);
Middleware Order Matters
// Order is important!
app.use(express.json()); // Parse JSON bodies
app.use(requestLogger); // Log all requests
app.use('/api', apiRoutes); // API routes
app.use(notFoundHandler); // 404 handler
app.use(errorHandler); // Error handler (always last)
Middleware runs in the order it's registered. Keep that in mind when debugging.