Chapter 02

路由系统

从基础 HTTP 方法到嵌套路由器,掌握 Hono 全球最快路由引擎的完整使用方式

基础路由

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 的路由匹配遵循以下优先级规则(由高到低):

  1. 精确匹配/users/me 优先于 /users/:id
  2. 静态前缀更长/users/active 优先于 /users/:id
  3. 命名参数/users/:id 优先于 /users/*
  4. 通配符/users/*
  5. 注册顺序:相同优先级下,先注册的先匹配
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.tsroutes/posts.ts),然后在主 index.ts 中通过 app.route() 挂载。这种结构清晰、易于测试,也便于在多个路由之间共享中间件。