AI 电子伴侣
创建时间: 2026-04-08最后更新: 2026-04-18

1. 中间件的基本形式

Hono 的中间件长这样:

index.ts
01
import { Hono } from 'hono'
02
03
const app = new Hono()
04
05
app.use('*', async (c, next) => {
06
// 请求到达路由之前,做点事情
07
console.log('请求进来了')
08
09
await next() // 把请求交给下一个中间件或路由处理
10
11
// 路由处理完了,响应出去之前,做点事情
12
console.log('响应出去了')
13
})
14
15
app.get('/', (c) => {
16
return c.text('Hello!')
17
})
18
19
export default app

核心就是 await next()。它把中间件分成两半:next() 之前处理请求,next() 之后处理下游中间件或路由返回后的阶段。

你也可以把它理解成一个分叉点:

  • 调用 await next():请求继续往后走,交给下一个中间件或最终路由
  • 直接 return c.json(...) / return c.text(...):当前中间件就把请求结束掉,后面的逻辑不再执行

鉴权、权限判断、参数拦截,本质上都是在这里决定"放行"还是"拦截"。

2. 洋葱模型

如果你用过 Koa,这个概念不陌生。多个中间件的执行顺序像剥洋葱:

index.ts
01
app.use('*', async (c, next) => {
02
console.log('中间件 A - 进')
03
await next()
04
console.log('中间件 A - 出')
05
})
06
07
app.use('*', async (c, next) => {
08
console.log('中间件 B - 进')
09
await next()
10
console.log('中间件 B - 出')
11
})
12
13
app.get('/', (c) => {
14
console.log('路由处理')
15
return c.text('Hello!')
16
})

控制台输出:

output.txt
1
中间件 A - 进
2
中间件 B - 进
3
路由处理
4
中间件 B - 出
5
中间件 A - 出

请求从外到内穿过中间件,响应从内到外返回。先注册的中间件最先接触请求、最后接触返回阶段。

3. 自定义中间件:请求计时

一个实用的中间件——记录每个请求花了多长时间:

index.ts
01
import { Hono } from 'hono'
02
03
const app = new Hono()
04
05
// 请求计时中间件
06
app.use('*', async (c, next) => {
07
const start = Date.now()
08
await next()
09
const duration = Date.now() - start
10
console.log(`${c.req.method} ${c.req.path} - ${duration}ms`)
11
})
12
13
app.get('/', (c) => {
14
return c.text('Hello!')
15
})
16
17
export default app

await next() 前记录开始时间,await next() 后计算耗时。洋葱模型的经典用法。

4. 自定义中间件:简单鉴权

检查请求头里有没有合法的 API Key:

index.ts
01
import { Hono } from 'hono'
02
03
const app = new Hono()
04
05
// 鉴权中间件
06
// 这里为了先讲清概念,先省略 Hono 的类型声明
07
const authMiddleware = async (c, next) => {
08
const apiKey = c.req.header('X-API-Key')
09
10
if (apiKey !== 'my-secret-key') {
11
return c.json({ error: 'Unauthorized' }, 401)
12
}
13
14
await next()
15
}
16
17
// 公开接口,不需要鉴权
18
app.get('/', (c) => {
19
return c.text('Public')
20
})
21
22
// 需要鉴权的接口
23
app.get('/api/secret', authMiddleware, (c) => {
24
return c.json({ data: 'This is secret' })
25
})
26
27
export default app

注意:如果鉴权失败,直接 return c.json(...) 就行,不调用 next(),请求就不会继续往下走。

如果你在 TypeScript 严格模式里写项目,后面最好把这类中间件补上类型,或者直接使用 Hono 提供的 createMiddleware() 来创建中间件。教程这里先把注意力放在执行流程本身。

5. 内置中间件

Hono 自带了一批开箱即用的中间件,不用自己造轮子:

index.ts
01
import { Hono } from 'hono'
02
import { cors } from 'hono/cors'
03
import { logger } from 'hono/logger'
04
import { timing } from 'hono/timing'
05
import { prettyJSON } from 'hono/pretty-json'
06
import { secureHeaders } from 'hono/secure-headers'
07
08
const app = new Hono()
09
10
// 请求日志:控制台打印每个请求的方法、路径、状态码、耗时
11
app.use('*', logger())
12
13
// 跨域配置:允许前端跨域访问
14
app.use('*', cors({
15
origin: 'http://localhost:3000',
16
allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
17
}))
18
19
// Server-Timing 头:响应头里带上各阶段耗时,浏览器 DevTools 可以看到
20
app.use('*', timing())
21
22
// 美化 JSON:请求 URL 加 ?pretty 参数时,返回格式化的 JSON
23
app.use('*', prettyJSON())
24
25
// 安全响应头:自动设置 X-Frame-Options、X-Content-Type-Options 等
26
app.use('*', secureHeaders())
27
28
app.get('/api/users', (c) => {
29
return c.json([
30
{ id: 1, name: 'Alice' },
31
{ id: 2, name: 'Bob' },
32
])
33
})
34
35
export default app

每个中间件干的事情:

  • logger():控制台打印 <-- GET /api/users--> GET /api/users 200 12ms
  • cors():处理跨域预检请求(OPTIONS),设置 Access-Control-Allow-* 响应头
  • timing():在响应头加 Server-Timing 字段,方便性能分析
  • prettyJSON():访问 /api/users?pretty 时返回缩进后的 JSON
  • secureHeaders():一键设置安全相关的 HTTP 头,防 XSS、点击劫持等

6. 中间件挂载范围

中间件可以作用在不同范围:

index.ts
01
import { Hono } from 'hono'
02
import { logger } from 'hono/logger'
03
import { cors } from 'hono/cors'
04
05
const app = new Hono()
06
07
// 全局中间件:所有路由都会经过
08
app.use('*', logger())
09
10
// 路径级中间件:只有 /api 开头的路由会经过
11
app.use('/api/*', cors())
12
13
// 鉴权中间件,只用在 /api 下
14
const auth = async (c, next) => {
15
const token = c.req.header('Authorization')
16
if (!token) {
17
return c.json({ error: 'Unauthorized' }, 401)
18
}
19
await next()
20
}
21
app.use('/api/*', auth)
22
23
// 单个路由级别:把中间件直接写在路由参数里
24
const adminOnly = async (c, next) => {
25
const role = c.req.header('X-Role')
26
if (role !== 'admin') {
27
return c.json({ error: 'Forbidden' }, 403)
28
}
29
await next()
30
}
31
32
app.delete('/api/users/:id', adminOnly, (c) => {
33
const id = c.req.param('id')
34
return c.json({ message: `User ${id} deleted` })
35
})
36
37
// 公开路由,不受 /api/* 的中间件影响
38
app.get('/', (c) => {
39
return c.text('Public homepage')
40
})
41
42
export default app

三种粒度:

  • app.use('*', ...):全局,所有请求都过
  • app.use('/api/*', ...):路径前缀匹配,只有 /api/ 下的请求会过
  • app.get('/path', middleware, handler):只对这一个路由生效

7. 执行顺序很重要

中间件按注册顺序执行。顺序搞错了会出问题:

index.ts
01
import { Hono } from 'hono'
02
import { cors } from 'hono/cors'
03
import { logger } from 'hono/logger'
04
05
const app = new Hono()
06
07
// 正确顺序:cors 在鉴权之前
08
// 因为浏览器跨域会先发 OPTIONS 预检请求
09
// 如果鉴权在 cors 之前,预检请求没带 token,直接被 401 了
10
app.use('*', logger())
11
app.use('*', cors())
12
app.use('/api/*', async (c, next) => {
13
const token = c.req.header('Authorization')
14
if (!token) {
15
return c.json({ error: 'Unauthorized' }, 401)
16
}
17
await next()
18
})
19
20
app.get('/api/data', (c) => {
21
return c.json({ message: 'Protected data' })
22
})
23
24
export default app

一般推荐的顺序:loggercorssecureHeaders → 鉴权 → 业务逻辑。日志最先,这样所有请求(包括被拦截的)都能被记录到。

8. 用 c.set / c.get 在中间件和路由之间传数据

中间件处理完的结果,怎么传给后面的路由?用 c.set()c.get()

index.ts
01
import { Hono } from 'hono'
02
03
const app = new Hono()
04
05
// 鉴权中间件:解析 token,把用户信息存到 context 里
06
app.use('/api/*', async (c, next) => {
07
const token = c.req.header('Authorization')
08
09
if (!token) {
10
return c.json({ error: 'Unauthorized' }, 401)
11
}
12
13
// 假设解析 token 得到了用户信息
14
const user = { id: 1, name: 'Alice', role: 'admin' }
15
16
// 把用户信息存到 context 里
17
c.set('user', user)
18
19
await next()
20
})
21
22
// 路由里通过 c.get() 拿到用户信息
23
app.get('/api/profile', (c) => {
24
const user = c.get('user')
25
return c.json({ user })
26
})
27
28
app.get('/api/admin', (c) => {
29
const user = c.get('user')
30
31
if (user.role !== 'admin') {
32
return c.json({ error: 'Forbidden' }, 403)
33
}
34
35
return c.json({ message: 'Welcome, admin' })
36
})
37
38
export default app

c.set() / c.get() 是中间件与路由之间的通信通道。数据只在当前请求的生命周期内有效,不同请求之间互不影响。

和前一篇一样,这里为了先理解机制,先省略了类型声明。真实项目里,像 user 这种在中间件里写入、在路由里读取的数据,最好补上类型,不然编辑器很难准确提示字段。

9. 总结

中间件是 Hono 处理横切关注点的核心机制。洋葱模型让你可以在请求前后都插入逻辑,c.set() / c.get() 解决了中间件和路由之间的数据传递。内置中间件覆盖了大部分常见需求,自定义中间件也就是一个 async 函数的事。

下一篇看数据校验——怎么优雅地验证请求参数和请求体。