中间件的复用与模块化
中间件的复用与模块化
Express中间件是处理HTTP请求的核心机制,通过将功能拆分为可复用的模块,能够显著提升代码的可维护性和开发效率。合理的中间件设计能让路由逻辑更清晰,避免重复代码,同时便于团队协作。
中间件的基本复用模式
Express中间件本质上是具有特定签名的函数,最简单的复用方式是将中间件函数提取为独立模块。例如,一个记录请求时间的中间件可以这样封装:
// middleware/requestTime.js
module.exports = function requestTime(req, res, next) {
req.requestTime = Date.now()
next()
}
// app.js
const requestTime = require('./middleware/requestTime')
app.use(requestTime)
这种模式特别适合需要在多个路由中重复使用的逻辑,如身份验证、数据预处理等。更复杂的中间件可以通过工厂函数实现配置化:
// middleware/logger.js
module.exports = function(format) {
return function(req, res, next) {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`)
next()
}
}
// app.js
const logger = require('./middleware/logger')
app.use(logger('combined'))
模块化中间件组合
多个相关中间件可以组合成模块包,通过index.js统一导出。例如构建API安全相关的中间件集合:
// middleware/security/index.js
const helmet = require('helmet')
const rateLimit = require('express-rate-limit')
module.exports = {
basicProtection: helmet(),
apiLimiter: rateLimit({
windowMs: 15 * 60 * 1000,
max: 100
}),
cors: require('./cors')
}
使用时可以整体引入或按需选择:
const { basicProtection, apiLimiter } = require('./middleware/security')
app.use(basicProtection)
app.use('/api/', apiLimiter)
路由级别的中间件封装
对于特定路由组的中间件,可以结合Router实现更精细的模块化。例如用户系统的路由模块:
// routes/users.js
const router = require('express').Router()
const auth = require('../middleware/auth')
const validator = require('../middleware/userValidator')
router.use(auth.required)
router.get('/profile', validator.getProfile, getUserProfile)
router.post('/update', validator.updateUser, updateUser)
module.exports = router
主应用文件只需挂载路由模块:
// app.js
app.use('/users', require('./routes/users'))
动态中间件加载机制
通过配置文件动态加载中间件可以实现环境差异化管理。创建middleware-loader.js:
const fs = require('fs')
const path = require('path')
module.exports = function(app) {
const env = process.env.NODE_ENV || 'development'
const config = require(`./config/${env}.json`)
config.middlewares.forEach(mw => {
const middleware = require(path.join(__dirname, mw.path))
app.use(middleware(config.options[mw.name]))
})
}
对应的配置文件示例:
// config/development.json
{
"middlewares": [
{ "name": "logger", "path": "./middleware/logger" },
{ "name": "debug", "path": "./middleware/debugTool" }
],
"options": {
"logger": { "level": "verbose" }
}
}
中间件的测试策略
可复用的中间件应当具备独立的可测试性。使用supertest进行HTTP层测试的示例:
// test/authMiddleware.test.js
const request = require('supertest')
const express = require('express')
const auth = require('../middleware/auth')
test('auth middleware rejects missing token', async () => {
const app = express()
app.get('/protected', auth.required, (req, res) => res.sendStatus(200))
const res = await request(app)
.get('/protected')
.expect(401)
expect(res.body.error).toBe('Unauthorized')
})
对于纯函数型中间件,可以直接调用测试:
// test/queryParser.test.js
const queryParser = require('../middleware/queryParser')
test('transforms comma-separated strings to arrays', () => {
const req = { query: { tags: 'js,node,express' } }
const next = jest.fn()
queryParser(req, {}, next)
expect(req.query.tags).toEqual(['js', 'node', 'express'])
expect(next).toHaveBeenCalled()
})
中间件的错误处理模式
错误处理中间件需要特殊的设计考量。创建可复用的错误处理器:
// middleware/errorHandler.js
module.exports = function(options = {}) {
return function(err, req, res, next) {
if (options.log) {
console.error('[ERROR]', err.stack)
}
res.status(err.status || 500).json({
error: options.expose ? err.message : 'Internal Server Error'
})
}
}
// app.js
const errorHandler = require('./middleware/errorHandler')
app.use(errorHandler({
expose: process.env.NODE_ENV === 'development'
}))
对于异步中间件,需要包装处理:
// middleware/asyncHandler.js
module.exports = function(handler) {
return function(req, res, next) {
Promise.resolve(handler(req, res, next))
.catch(next)
}
}
// routes/users.js
router.get('/:id', asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id)
if (!user) throw new Error('Not found')
res.json(user)
}))
中间件的性能优化
高频使用的中间件需要考虑性能优化。例如缓存静态资源的ETag计算:
// middleware/staticCache.js
const crypto = require('crypto')
const fs = require('fs').promises
module.exports = function(root) {
const cache = new Map()
return async function(req, res, next) {
if (req.method !== 'GET') return next()
const filePath = path.join(root, req.path)
try {
let etag = cache.get(filePath)
const stats = await fs.stat(filePath)
if (!etag || etag.mtime < stats.mtimeMs) {
const content = await fs.readFile(filePath)
etag = {
value: crypto.createHash('md5').update(content).digest('hex'),
mtime: stats.mtimeMs
}
cache.set(filePath, etag)
}
res.set('ETag', etag.value)
if (req.headers['if-none-match'] === etag.value) {
return res.sendStatus(304)
}
} catch (err) {
// 文件不存在等错误继续传递
}
next()
}
}
中间件的类型安全
在TypeScript项目中,可以定义中间件类型约束:
// types/middleware.ts
import { Request, Response, NextFunction } from 'express'
export interface Middleware {
(req: Request, res: Response, next: NextFunction): void
}
export interface AsyncMiddleware {
(req: Request, res: Response, next: NextFunction): Promise<void>
}
// middleware/auth.ts
const authMiddleware: Middleware = (req, res, next) => {
if (!req.headers.authorization) {
return res.status(401).json({ error: 'Unauthorized' })
}
next()
}
中间件的依赖注入
对于需要外部依赖的中间件,可以采用依赖注入模式:
// middleware/dbContext.js
module.exports = function(db) {
return function(req, res, next) {
req.db = {
users: db.collection('users'),
posts: db.collection('posts')
}
next()
}
}
// app.js
const MongoClient = require('mongodb').MongoClient
const dbContext = require('./middleware/dbContext')
MongoClient.connect(uri, (err, client) => {
const db = client.db('myapp')
app.use(dbContext(db))
})
中间件的AOP实践
面向切面编程在中间件中尤为适用。例如实现方法耗时统计:
// middleware/benchmark.js
module.exports = function(label) {
return function(req, res, next) {
const start = process.hrtime()
res.on('finish', () => {
const diff = process.hrtime(start)
console.log(`${label} took ${diff[0] * 1e3 + diff[1] / 1e6}ms`)
})
next()
}
}
// routes/api.js
router.use(require('../middleware/benchmark')('API'))
router.get('/data', /* ... */)
中间件的版本兼容
处理API版本兼容的中间件设计:
// middleware/versioning.js
module.exports = function(options) {
return function(req, res, next) {
const version = req.get('X-API-Version') || options.default
if (!options.supported.includes(version)) {
return res.status(406).json({
error: `Unsupported API version. Supported: ${options.supported.join(', ')}`
})
}
req.apiVersion = version
res.set('X-API-Version', version)
next()
}
}
// app.js
app.use(require('./middleware/versioning')({
default: 'v1',
supported: ['v1', 'v2']
}))
中间件的元编程应用
利用Proxy实现动态中间件属性:
// middleware/featureToggle.js
module.exports = function(features) {
return function(req, res, next) {
req.features = new Proxy({}, {
get(target, name) {
return features.includes(name)
? require(`./features/${name}`)
: null
}
})
next()
}
}
// routes/admin.js
router.post('/audit', (req, res) => {
if (req.features.advancedAudit) {
req.features.advancedAudit.log(req.body)
}
// ...
})
中间件的生命周期扩展
为中间件添加生命周期钩子:
// middleware/lifecycle.js
module.exports = function() {
const hooks = {
pre: [],
post: []
}
const middleware = function(req, res, next) {
runHooks('pre', req)
.then(() => next())
.then(() => runHooks('post', req))
.catch(next)
}
middleware.hook = function(type, fn) {
hooks[type].push(fn)
return this
}
async function runHooks(type, req) {
for (const hook of hooks[type]) {
await hook(req)
}
}
return middleware
}
// app.js
const lifecycle = require('./middleware/lifecycle')()
lifecycle.hook('pre', req => console.log('Request started'))
lifecycle.hook('post', req => console.log('Request completed'))
app.use(lifecycle)
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:异步中间件的实现方式
下一篇:常用中间件库推荐与比较