Development methods for custom middleware
Understanding the Basic Concepts of Middleware
Express middleware is essentially a function that has access to the request object (req
), the response object (res
), and the next middleware function in the application's request-response cycle (next
). Middleware can perform the following operations:
- Execute any code
- Modify the request and response objects
- End the request-response cycle
- Call the next middleware in the stack
function simpleMiddleware(req, res, next) {
console.log('Request received at:', new Date());
next(); // Must call next() to proceed to the next middleware
}
Types of Middleware
Express middleware can be broadly categorized into the following types:
- Application-level middleware: Bound to the application instance using
app.use()
orapp.METHOD()
- Router-level middleware: Similar to application-level middleware but bound to an
express.Router()
instance - Error-handling middleware: Specialized middleware for handling errors, accepting four parameters (
err
,req
,res
,next
) - Built-in middleware: Middleware included with Express, such as
express.static
- Third-party middleware: Community-developed middleware, such as
body-parser
Basic Steps for Creating Custom Middleware
Developing custom middleware typically follows these steps:
- Define a middleware function that accepts
req
,res
, andnext
parameters - Implement the desired functionality within the function body
- Decide whether to call
next()
to pass control - Mount the middleware to the application or router
// Example: Middleware to log request information
function requestLogger(req, res, next) {
const { method, originalUrl, ip } = req;
console.log(`[${new Date().toISOString()}] ${method} ${originalUrl} from ${ip}`);
next();
}
// Using the middleware
app.use(requestLogger);
Execution Order of Middleware
The order in which middleware is executed is crucial. Express executes middleware in the order they are registered:
app.use(middleware1); // Executes first
app.use(middleware2); // Executes next
app.get('/path', middleware3, (req, res) => { // Executes last
res.send('Hello World');
});
Developing Error-Handling Middleware
Error-handling middleware has four parameters and must be placed after other middleware:
function errorHandler(err, req, res, next) {
console.error(err.stack);
// Return different responses based on error type
if (err instanceof CustomError) {
return res.status(400).json({ error: err.message });
}
res.status(500).send('Something broke!');
}
// Using error-handling middleware (must be placed last)
app.use(errorHandler);
Handling Asynchronous Middleware
Modern Express supports asynchronous middleware using async/await
:
async function asyncMiddleware(req, res, next) {
try {
const data = await fetchSomeData();
req.data = data;
next();
} catch (err) {
next(err); // Pass the error to the error-handling middleware
}
}
// Or a more concise version
const asyncMiddleware = async (req, res, next) => {
req.user = await User.findById(req.params.id);
next();
};
Configurable Middleware Options
Configurable middleware is more flexible and often uses a factory function pattern:
function createLogger(options = {}) {
// Default configuration
const defaults = {
logLevel: 'info',
timestamp: true
};
// Merge configurations
const config = { ...defaults, ...options };
// Return the middleware function
return function(req, res, next) {
let message = `${req.method} ${req.url}`;
if (config.timestamp) {
message = `[${new Date().toISOString()}] ${message}`;
}
if (config.logLevel === 'debug') {
console.debug(message, { headers: req.headers });
} else {
console.log(message);
}
next();
};
}
// Using configurable middleware
app.use(createLogger({ logLevel: 'debug' }));
Combining Middleware
Multiple middleware functions can be combined into a middleware chain:
function validateAuth(req, res, next) {
if (!req.headers.authorization) {
return res.status(401).send('Unauthorized');
}
next();
}
function parseUser(req, res, next) {
const token = req.headers.authorization.split(' ')[1];
req.user = decodeToken(token);
next();
}
// Combining middleware
app.get('/profile', validateAuth, parseUser, (req, res) => {
res.json(req.user.profile);
});
// Or using an array
const authMiddlewares = [validateAuth, parseUser];
app.get('/dashboard', authMiddlewares, (req, res) => {
res.render('dashboard', { user: req.user });
});
Performance Optimization for Middleware
When writing high-performance middleware, consider the following:
- Avoid blocking operations
- Use caching appropriately
- Return responses early when possible
- Minimize unnecessary processing
// Example of caching middleware
function createCacheMiddleware(ttl = 60) {
const cache = new Map();
return function(req, res, next) {
const key = req.originalUrl;
// Check cache
if (cache.has(key)) {
const { data, expires } = cache.get(key);
if (expires > Date.now()) {
return res.send(data); // Return cached data directly
}
}
// Override res.send to capture response data
const originalSend = res.send;
res.send = function(body) {
cache.set(key, {
data: body,
expires: Date.now() + ttl * 1000
});
originalSend.call(this, body);
};
next();
};
}
Testing Middleware
Middleware can be tested using libraries like supertest
:
const request = require('supertest');
const express = require('express');
describe('Auth Middleware', () => {
let app;
beforeEach(() => {
app = express();
app.use(express.json());
app.use(require('./authMiddleware'));
app.get('/test', (req, res) => res.send('OK'));
});
it('should reject requests without token', async () => {
const res = await request(app)
.get('/test')
.expect(401);
expect(res.body.error).toBe('Unauthorized');
});
it('should allow requests with valid token', async () => {
const res = await request(app)
.get('/test')
.set('Authorization', 'Bearer valid-token')
.expect(200);
expect(res.text).toBe('OK');
});
});
Practical Use Cases for Middleware
- Request validation middleware:
function validateRequest(schema) {
return function(req, res, next) {
const { error } = schema.validate(req.body);
if (error) {
return res.status(400).json({
error: error.details[0].message
});
}
next();
};
}
// Using Joi to define schema
const Joi = require('joi');
const userSchema = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required(),
password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{3,30}$'))
});
// Applying middleware
app.post('/register', validateRequest(userSchema), (req, res) => {
// Handle registration logic
});
- Response time middleware:
function responseTime() {
return function(req, res, next) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
res.setHeader('X-Response-Time', `${duration}ms`);
console.log(`${req.method} ${req.url} - ${duration}ms`);
});
next();
};
}
app.use(responseTime());
- API version control middleware:
function apiVersion(version) {
return function(req, res, next) {
req.apiVersion = version;
// Load different routes based on version
if (version === 'v1') {
require('./routes/v1')(req, res, next);
} else if (version === 'v2') {
require('./routes/v2')(req, res, next);
} else {
next(); // Proceed to other middleware
}
};
}
// Using middleware
app.use('/api', apiVersion('v2'));
Advanced Middleware Patterns
- Conditional middleware:
function conditionalMiddleware(condition, middleware) {
return function(req, res, next) {
if (condition(req)) {
return middleware(req, res, next);
}
next();
};
}
// Example: Enable logging only in development
app.use(conditionalMiddleware(
req => process.env.NODE_ENV === 'development',
require('morgan')('dev')
));
- Middleware pipeline:
function pipeMiddlewares(...middlewares) {
return function(req, res, next) {
let index = 0;
function runMiddleware() {
if (index < middlewares.length) {
const middleware = middlewares[index++];
middleware(req, res, runMiddleware);
} else {
next();
}
}
runMiddleware();
};
}
// Using pipeline
app.use(pipeMiddlewares(
middleware1,
middleware2,
middleware3
));
- Terminable middleware:
function terminableMiddleware(options = {}) {
return function(req, res, next) {
let terminated = false;
// Add termination method
req.terminate = (error) => {
terminated = true;
next(error);
};
// Wrap next method
const originalNext = next;
next = function(err) {
if (!terminated) {
originalNext(err);
}
};
// Execute middleware logic
if (options.preCheck) {
if (!options.preCheck(req)) {
return req.terminate(new Error('Pre-check failed'));
}
}
// Continue normal flow
next();
};
}
Middleware and Express 5 New Features
Express 5 introduces several improvements for middleware development:
- Asynchronous error handling: No need to explicitly catch async errors
- Improved routing system: More flexible route matching
- Better Promise support: Middleware can return Promises
// Example of async middleware in Express 5
app.use(async (req, res, next) => {
// Automatically catches async errors
const user = await User.findById(req.params.id);
req.user = user;
next();
});
// Error handling is more concise
app.use((err, req, res, next) => {
// Automatically handles async errors
res.status(500).send(err.message);
});
Best Practices for Middleware Development
- Single responsibility principle: Each middleware should do one thing
- Keep it simple: Avoid excessive logic in middleware
- Proper error handling: Always handle errors correctly
- Performance considerations: Avoid blocking operations
- Documentation: Write clear documentation for middleware
- Configurability: Make middleware as flexible as possible
- Test coverage: Write comprehensive tests for middleware
// Example of good practices: API key validation middleware
function createApiKeyAuth(validKeys) {
if (!Array.isArray(validKeys)) {
throw new Error('validKeys must be an array');
}
return function(req, res, next) {
const apiKey = req.get('X-API-Key');
if (!apiKey) {
return res.status(401).json({ error: 'API key missing' });
}
if (!validKeys.includes(apiKey)) {
return res.status(403).json({ error: 'Invalid API key' });
}
next();
};
}
// Usage example
const API_KEYS = process.env.API_KEYS.split(',');
app.use(createApiKeyAuth(API_KEYS));
Middleware in Microservices Architecture
In a microservices architecture, middleware can be used for:
- Inter-service communication: Handling requests between services
- Load balancing: Distributing requests to different service instances
- Circuit breaker pattern: Preventing cascading failures
- Service discovery: Dynamically routing to available services
// Example of microservice gateway middleware
function serviceProxy(serviceMap) {
const httpProxy = require('http-proxy-middleware');
return function(req, res, next) {
const serviceName = req.path.split('/')[1];
if (serviceMap[serviceName]) {
return httpProxy({
target: serviceMap[serviceName],
pathRewrite: {
[`^/${serviceName}`]: ''
}
})(req, res, next);
}
next();
};
}
// Usage example
app.use(serviceProxy({
users: 'http://user-service:3000',
products: 'http://product-service:3001'
}));
Debugging Middleware Techniques
When debugging middleware, consider these methods:
- Logging: Detailed logging of middleware execution
- Breakpoint debugging: Using Node.js debugging tools
- Isolated testing: Testing middleware functionality in isolation
- Performance profiling: Measuring middleware execution time
// Example of debugging middleware
function debugMiddleware(label) {
return function(req, res, next) {
console.time(label);
res.on('finish', () => {
console.timeEnd(label);
console.log('Request details:', {
method: req.method,
url: req.originalUrl,
status: res.statusCode,
headers: req.headers
});
});
next();
};
}
// Usage example
app.use(debugMiddleware('request'));
Security Considerations for Middleware
When developing middleware, consider these security aspects:
- Input validation: Always validate user input
- Sensitive data handling: Handle sensitive information carefully
- Dependency security: Keep dependencies updated
- Error messages: Avoid leaking sensitive error information
// Example of security middleware: Preventing sensitive information leaks
function securityHeaders() {
return function(req, res, next) {
res.set({
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Strict-Transport-Security': 'max-age=63072000; includeSubDomains; preload',
'Content-Security-Policy': "default-src 'self'"
});
// Remove headers that may leak information
res.removeHeader('X-Powered-By');
next();
};
}
// Usage example
app.use(securityHeaders());
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:内置中间件与第三方中间件
下一篇:中间件的执行顺序与控制