基础路由
Hono 的路由 API 简洁直观。每个路由方法接收路径模式和处理函数(Handler),处理函数的参数是 Context 对象 c:
import { Hono } from 'hono'
const app = new Hono()
// GET
app.get('/users', (c) => c.json({ users: [] }))
// POST
app.post('/users', async (c) => {
const body = await c.req.json()
return c.json({ created: body }, 201)
})
// PUT
app.put('/users/:id', async (c) => {
const id = c.req.param('id')
return c.json({ updated: id })
})
// DELETE
app.delete('/users/:id', (c) => {
return c.json({ deleted: c.req.param('id') })
})
// PATCH
app.patch('/users/:id', async (c) => {
const body = await c.req.json()
return c.json({ patched: body })
})
// ALL — 匹配所有 HTTP 方法
app.all('/health', (c) => c.text('OK'))
export default app
路径参数
命名参数
// :id 捕获单段路径
app.get('/users/:id', (c) => {
const id = c.req.param('id')
return c.json({ id })
})
// GET /users/42 → { "id": "42" }
// 获取所有参数
app.get('/posts/:year/:month/:slug', (c) => {
const { year, month, slug } = c.req.param()
return c.json({ year, month, slug })
})
// GET /posts/2024/01/hello → { year:"2024", month:"01", slug:"hello" }
通配符
// * 匹配剩余路径(任意深度)
app.get('/static/*', (c) => {
const path = c.req.param('*')
return c.text(`Serving: ${path}`)
})
// GET /static/img/logo.png → "Serving: img/logo.png"
可选参数
// :id? 表示可选参数
app.get('/books/:genre?', (c) => {
const genre = c.req.param('genre') // 可能为 undefined
if (genre) {
return c.json({ filter: genre })
}
return c.json({ all: true })
})
// GET /books → { all: true }
// GET /books/scifi → { filter: "scifi" }
查询参数
app.get('/search', (c) => {
const q = c.req.query('q') // 单个参数
const page = c.req.query('page') // 单个参数
const tags = c.req.queries('tag') // 多值参数: ?tag=a&tag=b
return c.json({ q, page, tags })
})
// GET /search?q=hono&page=1&tag=ts&tag=web
// → { q: "hono", page: "1", tags: ["ts", "web"] }
路由组
路由组允许为一组路由添加公共前缀,是组织大型 API 的关键工具:
import { Hono } from 'hono'
const app = new Hono()
// 创建独立的路由器实例
const apiV1 = new Hono()
apiV1.get('/users', (c) => c.json({ version: 'v1', users: [] }))
apiV1.post('/users', async (c) => {
return c.json({ created: true }, 201)
})
// 挂载到主 app,所有路由自动加上 /api/v1 前缀
app.route('/api/v1', apiV1)
// GET /api/v1/users → { version: "v1", users: [] }
// POST /api/v1/users → { created: true }
嵌套路由器
Hono 的路由器可以任意嵌套,适合模块化大型应用:
// routes/users.ts
import { Hono } from 'hono'
const users = new Hono()
users.get('/', (c) => c.json({ users: [] }))
users.get('/:id', (c) => c.json({ id: c.req.param('id') }))
users.post('/', async (c) => c.json(await c.req.json(), 201))
export default users
// routes/posts.ts
import { Hono } from 'hono'
const posts = new Hono()
posts.get('/', (c) => c.json({ posts: [] }))
posts.get('/:id', (c) => c.json({ id: c.req.param('id') }))
export default posts
// src/index.ts — 主入口
import { Hono } from 'hono'
import users from './routes/users'
import posts from './routes/posts'
const app = new Hono()
const api = new Hono()
api.route('/users', users)
api.route('/posts', posts)
app.route('/api', api)
// 最终路由:
// GET /api/users → users.get('/')
// GET /api/users/:id → users.get('/:id')
// GET /api/posts → posts.get('/')
export default app
路由优先级与匹配顺序
Hono 的路由匹配遵循以下优先级规则(由高到低):
- 精确匹配:
/users/me优先于/users/:id - 静态前缀更长:
/users/active优先于/users/:id - 命名参数:
/users/:id优先于/users/* - 通配符:
/users/* - 注册顺序:相同优先级下,先注册的先匹配
app.get('/users/me', (c) => c.json({ user: 'current' })) // 精确匹配,最高优先
app.get('/users/:id', (c) => c.json({ id: c.req.param('id') })) // 参数匹配
app.get('/users/*', (c) => c.json({ wildcard: true })) // 通配,最低优先
// GET /users/me → { user: "current" } ✓ 精确匹配
// GET /users/42 → { id: "42" } ✓ 参数匹配
RegExp 路由(最高性能)
Hono 默认使用 RegExpRouter,它将所有路由在初始化时编译为一个巨型正则表达式,匹配时只需一次 exec 调用,时间复杂度接近 O(1):
import { Hono } from 'hono'
import { RegExpRouter } from 'hono/router/reg-exp-router'
// 显式指定路由器(通常不需要,默认已使用)
const app = new Hono({ router: new RegExpRouter() })
// 对于 Cloudflare Workers(每次请求重新初始化),
// 使用 LinearRouter 可减少路由注册时间
import { LinearRouter } from 'hono/router/linear-router'
const cfApp = new Hono({ router: new LinearRouter() })
实战:构建 RESTful 资源路由
以用户资源(/users)为例,构建完整的 CRUD API:
// routes/users.ts
import { Hono } from 'hono'
// 模拟数据库
const db = new Map<string, { id: string; name: string; email: string }>()
const users = new Hono()
// GET /users — 列表
users.get('/', (c) => {
return c.json({
users: Array.from(db.values()),
total: db.size,
})
})
// GET /users/:id — 详情
users.get('/:id', (c) => {
const user = db.get(c.req.param('id'))
if (!user) {
return c.json({ error: 'User not found' }, 404)
}
return c.json(user)
})
// POST /users — 创建
users.post('/', async (c) => {
const body = await c.req.json<{ name: string; email: string }>()
const id = crypto.randomUUID()
const user = { id, ...body }
db.set(id, user)
return c.json(user, 201)
})
// PUT /users/:id — 全量更新
users.put('/:id', async (c) => {
const id = c.req.param('id')
if (!db.has(id)) {
return c.json({ error: 'User not found' }, 404)
}
const body = await c.req.json<{ name: string; email: string }>()
db.set(id, { id, ...body })
return c.json(db.get(id))
})
// DELETE /users/:id — 删除
users.delete('/:id', (c) => {
const id = c.req.param('id')
const deleted = db.delete(id)
if (!deleted) {
return c.json({ error: 'User not found' }, 404)
}
return c.body(null, 204) // 204 No Content
})
export default users
路由设计建议:将每个资源的路由放在独立文件(routes/users.ts、routes/posts.ts),然后在主 index.ts 中通过 app.route() 挂载。这种结构清晰、易于测试,也便于在多个路由之间共享中间件。