中间件组合模式
要点
- 中间件可以组合使用,把多个小中间件串成一条处理链路
- 工厂函数模式让中间件接收配置,返回真正的处理函数
compose()可以把多个中间件合并成一个,便于复用- 依赖注入通过
c.set()/c.get()在中间件之间传递数据 - 作用域隔离通过子应用和路径前缀实现,避免全局污染
- 中间件组合的核心是「单一职责」——每个中间件只做一件事
1. 链式组合
最基本的组合方式:按顺序挂载多个中间件。
// src/index.ts
import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'
import { secureHeaders } from 'hono/secure-headers'
import { requestId } from 'hono/request-id'
import { jwt } from 'hono/jwt'
const app = new Hono()
// 链式组合:按顺序执行
app.use('*', logger())
app.use('*', requestId())
app.use('*', cors())
app.use('*', secureHeaders())
app.use('/api/*', jwt({ secret: 'my-secret' }))
app.get('/api/profile', (c) => {
const payload = c.get('jwtPayload')
return c.json({ payload })
})
export default app每个中间件只做一件事:日志、请求 ID、CORS、安全头、鉴权。职责清晰,容易测试和替换。
2. 工厂函数模式
工厂函数让中间件接收配置,返回真正的处理函数。Hono 内置中间件大量使用这种模式:
// src/middleware/timing.ts
import { createMiddleware } from 'hono/factory'
type TimingOptions = {
headerName?: string
includeBody?: boolean
}
export const timing = (options: TimingOptions = {}) => {
const headerName = options.headerName ?? 'X-Response-Time'
return createMiddleware(async (c, next) => {
const start = Date.now()
await next()
const duration = Date.now() - start
c.header(headerName, `${duration}ms`)
if (options.includeBody) {
// 附加逻辑
}
})
}// src/index.ts
import { Hono } from 'hono'
import { timing } from './middleware/timing'
const app = new Hono()
// 不同路由使用不同配置
app.use('/api/*', timing({ headerName: 'X-Api-Time' }))
app.use('/admin/*', timing({ headerName: 'X-Admin-Time', includeBody: true }))
export default app工厂函数的优势:
- 配置灵活:同一个中间件可以根据参数调整行为
- 类型安全:配置项有类型推导
- 懒初始化:配置参数在注册时确定,运行时不再变化
3. compose 合并中间件
如果一组中间件经常一起使用,可以用 compose() 合并成一个:
// src/middleware/common.ts
import { compose } from 'hono/compose'
import { logger } from 'hono/logger'
import { requestId } from 'hono/request-id'
import { cors } from 'hono/cors'
import { secureHeaders } from 'hono/secure-headers'
// 合并成一个中间件
export const commonMiddleware = compose(
logger(),
requestId(),
cors(),
secureHeaders(),
)// src/index.ts
import { Hono } from 'hono'
import { commonMiddleware } from './middleware/common'
const app = new Hono()
// 一行挂载多个中间件
app.use('*', commonMiddleware)
export default appcompose() 的等价写法是手动串联:
app.use('*', logger())
app.use('*', requestId())
app.use('*', cors())
app.use('*', secureHeaders())compose() 的优势是把相关的中间件组织到一个命名下,便于在多个 app 实例之间复用。
4. 依赖注入
中间件之间通过 c.set() / c.get() 传递数据。前一个中间件写入,后一个中间件或路由读取:
// src/middleware/auth.ts
import { createMiddleware } from 'hono/factory'
type User = { id: string; role: string }
export const auth = createMiddleware<{ Variables: { user: User } }>(
async (c, next) => {
const token = c.req.header('Authorization')?.replace('Bearer ', '')
if (!token) return c.json({ error: 'Unauthorized' }, 401)
// 验证 token,得到用户信息
const user: User = { id: '1', role: 'admin' }
c.set('user', user)
await next()
}
)// src/middleware/permission.ts
import { createMiddleware } from 'hono/factory'
type User = { id: string; role: string }
export function requireRole(role: string) {
return createMiddleware<{ Variables: { user: User } }>(
async (c, next) => {
const user = c.get('user')
if (user.role !== role) {
return c.json({ error: 'Forbidden' }, 403)
}
await next()
}
)
}// src/index.ts
import { Hono } from 'hono'
import { auth } from './middleware/auth'
import { requireRole } from './middleware/permission'
const app = new Hono()
// auth 注入 user,requireRole 读取 user
app.use('/admin/*', auth)
app.use('/admin/*', requireRole('admin'))
app.get('/admin/dashboard', (c) => {
const user = c.get('user')
return c.json({ message: `Welcome, ${user.id}` })
})
export default app依赖注入的关键是类型安全。通过 createMiddleware<{ Variables: { user: User } }> 声明中间件依赖的变量,编辑器可以检查类型是否匹配。
5. 作用域隔离
全局中间件影响所有路由,有时需要把中间件限制在特定范围:
5.1 路径前缀
// 只对 /api 路由生效
app.use('/api/*', cors())
app.use('/api/*', auth())
// 公开路由不受影响
app.get('/', (c) => c.text('Public'))5.2 子应用
// src/apps/admin.ts
import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { requireRole } from '../middleware/permission'
const admin = new Hono()
// 子应用的中间件只对子路由生效
admin.use('*', logger())
admin.use('*', requireRole('admin'))
admin.get('/dashboard', (c) => c.json({ admin: true }))
export default admin// src/index.ts
import { Hono } from 'hono'
import admin from './apps/admin'
const app = new Hono()
app.route('/admin', admin)
export default app子应用的中间件与主应用的中间件叠加。访问 /admin/dashboard 时,先经过主应用的全局中间件,再经过子应用的中间件。
5.3 路由级
// 只对这一条路由生效
app.get('/api/special', auth(), requireRole('admin'), (c) => {
return c.json({ special: true })
})路由级中间件的作用域最精确,但同一个中间件如果多条路由都需要,要写多次。
6. 中间件工厂
工厂模式可以生成一系列相关的中间件:
// src/middleware/rate-limit-factory.ts
import { rateLimit } from 'hono/rate-limit'
import type { MiddlewareHandler } from 'hono'
type RateLimitConfig = {
duration: number
max: number
}
const configs: Record<string, RateLimitConfig> = {
strict: { duration: 60 * 1000, max: 5 },
normal: { duration: 60 * 1000, max: 100 },
relaxed: { duration: 60 * 1000, max: 500 },
}
export function rateLimitByLevel(level: keyof typeof configs): MiddlewareHandler {
const config = configs[level]
return rateLimit(config)
}// src/index.ts
import { Hono } from 'hono'
import { rateLimitByLevel } from './middleware/rate-limit-factory'
const app = new Hono()
app.post('/api/login', rateLimitByLevel('strict'))
app.get('/api/users', rateLimitByLevel('normal'))
app.get('/api/config', rateLimitByLevel('relaxed'))
export default app工厂模式把配置和业务逻辑分离,中间件的使用者只需要选择策略名称。
7. 中间件管道
把一组中间件封装成一个「管道」,处理特定类型的请求:
// src/middleware/api-pipeline.ts
import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'
import { auth } from './auth'
import { requestLogger } from './request-logger'
export function createApiPipeline() {
const pipeline: MiddlewareHandler[] = [
logger(),
cors(),
auth(),
requestLogger(),
]
return (app: Hono) => {
pipeline.forEach((mw) => app.use('/api/*', mw))
}
}// src/index.ts
import { Hono } from 'hono'
import { createApiPipeline } from './middleware/api-pipeline'
const app = new Hono()
// 应用 API 管道
createApiPipeline()(app)
app.get('/api/users', (c) => c.json([]))
export default app管道模式把一组常用的中间件组织到一起,便于在不同项目之间复用。
8. 条件中间件
有些中间件只在特定条件下生效:
// src/middleware/conditional.ts
import { createMiddleware } from 'hono/factory'
export function conditionalMiddleware(
condition: (c: Context) => boolean,
middleware: MiddlewareHandler,
): MiddlewareHandler {
return createMiddleware(async (c, next) => {
if (condition(c)) {
// 调用条件满足时的中间件
return middleware(c, next)
}
await next()
})
}// 只在开发环境启用详细日志
app.use('*', conditionalMiddleware(
(c) => process.env.NODE_ENV === 'development',
detailedLogger()
))
// 只对 POST 请求启用 body 解析
app.use('*', conditionalMiddleware(
(c) => c.req.method === 'POST',
bodyParser()
))条件中间件避免在中间件内部写 if 判断,保持中间件本身的纯粹性。
9. 中间件顺序的影响
中间件顺序影响执行顺序,这在前面几节已经讨论过。组合模式里需要注意:
- 全局中间件在前:logger、requestId、cors、secureHeaders
- 业务中间件在后:auth、rateLimit、bodyParser
- 路由级最后:特定路由的中间件
// ✅ 正确顺序
app.use('*', logger())
app.use('*', requestId())
app.use('*', cors())
app.use('*', secureHeaders())
app.use('/api/*', auth())
app.use('/api/*', rateLimit())
app.post('/api/data', bodyParser(), handler)总结
中间件组合模式是组织复杂请求处理逻辑的关键。Hono 提供了多种组合方式,从简单的链式挂载到工厂函数、compose 合并、依赖注入。
这一节涉及到的几种模式:
- 链式组合:按顺序挂载多个中间件,最基础的方式
- 工厂函数:中间件接收配置,返回处理函数
- compose 合并:把多个中间件合并成一个,便于复用
- 依赖注入:通过
c.set()/c.get()在中间件之间传递数据 - 作用域隔离:路径前缀、子应用、路由级控制中间件的影响范围
- 中间件工厂:生成一系列相关的中间件
- 中间件管道:封装一组常用的中间件
- 条件中间件:只在特定条件下生效
组合模式的核心是「单一职责」——每个中间件只做一件事,通过组合实现复杂逻辑。这样的中间件容易测试、容易替换、容易理解。
下一篇看 AI 项目中的中间件实践——token 计量、流式转发、prompt 缓存、多租户鉴权。