The basic concept and working principle of middleware
Middleware is a core concept in the Express framework, used to insert custom logic during the processing of HTTP requests and responses. It enables preprocessing, post-processing, or interception of requests through chained calls, providing flexible extensibility for web applications.
Definition and Role of Middleware
Middleware is essentially a function with a specific signature, accepting three parameters: req
(request object), res
(response object), and next
(callback function). Its typical structure is as follows:
function middleware(req, res, next) {
// Processing logic
next(); // Pass control to the next middleware
}
Its primary roles include:
- Modifying request or response objects (e.g., adding headers)
- Terminating the request-response cycle (e.g., returning 403 on failed authorization)
- Invoking the next middleware (via
next()
)
Workflow of Middleware
The request-handling process in an Express application can be viewed as a middleware pipeline. When an HTTP request arrives, it is executed sequentially in the order the middleware is registered:
- The request enters the first middleware
- After processing, the middleware calls
next()
- Control is transferred to the next middleware
- The process repeats until the response is sent
Example workflow:
app.use((req, res, next) => {
console.log('First middleware');
next();
});
app.use((req, res, next) => {
console.log('Second middleware');
next();
});
app.get('/', (req, res) => {
res.send('Hello World');
});
When requesting /
, the output will sequentially display "First middleware" and "Second middleware."
Types of Middleware
Application-Level Middleware
Bound to the application instance via app.use()
or app.METHOD()
:
// Applies to all requests
app.use((req, res, next) => {
console.log('Time:', Date.now());
next();
});
// Applies only to GET /user
app.get('/user', (req, res, next) => {
res.send('USER');
});
Router-Level Middleware
Acts on specific route paths:
const userRouter = express.Router();
userRouter.use((req, res, next) => {
console.log('User route middleware');
next();
});
app.use('/users', userRouter);
Error-Handling Middleware
Accepts four parameters (err, req, res, next
):
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
Built-in Middleware
Out-of-the-box middleware provided by Express:
express.json()
: Parses JSON request bodiesexpress.urlencoded()
: Parses URL-encoded request bodiesexpress.static()
: Serves static files
app.use(express.json());
app.use(express.static('public'));
Third-Party Middleware
Community-provided extensions:
const morgan = require('morgan');
app.use(morgan('dev')); // Logging
Execution Order of Middleware
The execution order of middleware strictly depends on the registration order. The following example demonstrates the importance of order:
// This middleware will never execute
app.use((req, res, next) => {
console.log('This will never run');
next();
});
// Because the previous middleware terminates the flow
app.use((req, res, next) => {
res.send('Early response');
});
// Correct order should be:
app.use((req, res, next) => {
const isValid = checkAuth(req);
if (!isValid) return res.status(401).end();
next();
});
app.get('/protected', (req, res) => {
res.send('Protected content');
});
Composition and Modularization of Middleware
Multiple middleware functions can be combined:
function logOriginalUrl(req, res, next) {
console.log('Request URL:', req.originalUrl);
next();
}
function logMethod(req, res, next) {
console.log('Request Type:', req.method);
next();
}
app.get('/user', [logOriginalUrl, logMethod], (req, res) => {
res.send('User Info');
});
Example of modular organization:
// logger.js
module.exports = function(req, res, next) {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
};
// app.js
const logger = require('./logger');
app.use(logger);
Error-Handling Mechanism of Middleware
Error-handling middleware requires special definition:
// Synchronous errors are automatically caught
app.get('/error', (req, res) => {
throw new Error('BROKEN');
});
// Asynchronous errors must be explicitly passed
app.get('/async-error', (req, res, next) => {
fs.readFile('/nonexistent', (err, data) => {
if (err) next(err); // Must manually pass the error
else res.send(data);
});
});
// Error-handling middleware
app.use((err, req, res, next) => {
res.status(500).json({ error: err.message });
});
Performance Optimization of Middleware
- Filter unnecessary middleware execution:
app.use('/api', (req, res, next) => {
// Only applies to /api paths
next();
});
- Use conditional middleware:
const isAdmin = (req, res, next) => {
if (req.user.role === 'admin') return next();
res.status(403).end();
};
app.get('/admin', isAdmin, (req, res) => {
res.send('Admin panel');
});
- Middleware caching:
const cache = {};
app.use('/data', (req, res, next) => {
const key = req.originalUrl;
if (cache[key]) return res.json(cache[key]);
// Simulate time-consuming operation
setTimeout(() => {
const data = { value: Math.random() };
cache[key] = data;
res.json(data);
}, 1000);
});
Practical Use Cases of Middleware
Authentication
const authenticate = (req, res, next) => {
const token = req.headers.authorization;
if (!token) return res.status(401).send('Unauthorized');
try {
req.user = verifyToken(token);
next();
} catch (err) {
res.status(403).send('Invalid token');
}
};
app.get('/profile', authenticate, (req, res) => {
res.json(req.user);
});
Request Data Processing
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.post('/submit', (req, res) => {
console.log(req.body); // Automatically parsed request body
res.send('Data received');
});
Response Time Monitoring
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.url} - ${duration}ms`);
});
next();
});
CORS Handling
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type,Authorization');
next();
});
Advanced Middleware Patterns
Middleware Factory
A function that returns middleware:
function createLogger(format) {
return function(req, res, next) {
console.log(`[${format}] ${req.method} ${req.url}`);
next();
};
}
app.use(createLogger('short'));
Configurable Middleware
function configurableMiddleware(options = {}) {
return function(req, res, next) {
if (options.logRequest) {
console.log(`${req.method} ${req.url}`);
}
if (options.addTimestamp) {
req.timestamp = Date.now();
}
next();
};
}
app.use(configurableMiddleware({
logRequest: true,
addTimestamp: true
}));
Middleware Composition Tool
const compose = require('koa-compose'); // Can also be self-implemented
const middleware1 = (req, res, next) => {
console.log('Middleware 1');
next();
};
const middleware2 = (req, res, next) => {
console.log('Middleware 2');
next();
};
app.use(compose([middleware1, middleware2]));
Testing Middleware
Using supertest to test middleware:
const request = require('supertest');
const app = require('../app');
describe('Auth Middleware', () => {
it('should block unauthorized access', async () => {
const res = await request(app)
.get('/protected')
.expect(401);
});
it('should allow authorized access', async () => {
const res = await request(app)
.get('/protected')
.set('Authorization', 'valid-token')
.expect(200);
});
});
Debugging Techniques for Middleware
- Use debugging placeholders:
app.use((req, res, next) => {
console.log('Request reached middleware');
next();
console.log('Request left middleware');
});
- Inspect the middleware stack:
app.use((req, res, next) => {
console.log('Current stack:', this._router.stack);
next();
});
- Use debugging tools:
const debug = require('debug')('app:middleware');
app.use((req, res, next) => {
debug('Request headers:', req.headers);
next();
});
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:性能优化与缓存策略
下一篇:内置中间件与第三方中间件