F FisherHub Docs

02. 路由与中间件

Hono 的路由系统灵活高效,中间件机制简洁统一。本文将从路由进阶用法开始,逐步深入中间件的编写与组合。

路由分组

当路由数量增多时,使用分组可以让代码结构更清晰:

import { Hono } from 'hono'

const app = new Hono()
const users = new Hono()
const posts = new Hono()

// 用户相关路由
users.get('/', (c) => c.json({ users: [] }))
users.get('/:id', (c) => c.json({ id: c.req.param('id') }))
users.post('/', async (c) => {
  const body = await c.req.json()
  return c.json(body, 201)
})

// 文章相关路由
posts.get('/', (c) => c.json({ posts: [] }))
posts.get('/:id', (c) => c.json({ id: c.req.param('id') }))

// 挂载到主应用
app.route('/api/users', users)
app.route('/api/posts', posts)

上面的代码等价于注册了以下路由:

  • GET /api/users
  • GET /api/users/:id
  • POST /api/users
  • GET /api/posts
  • GET /api/posts/:id

路由优先级

Hono 按照注册顺序匹配路由。静态路由优先于动态路由,显式声明的优先级最高:

app.get('/users/me', (c) => c.text('当前用户'))
app.get('/users/:id', (c) => c.text('用户 ' + c.req.param('id')))

访问 /users/me 时,即使 me 可以被 :id 匹配到,Hono 也会优先匹配静态路由 /users/me

中间件基础

中间件是在路由处理器之前或之后执行的函数。Hono 的中间件是一个接收上下文对象和 next 函数的异步函数:

import { Hono, type MiddlewareHandler } from 'hono'

const logger: MiddlewareHandler = async (c, next) => {
  const start = Date.now()
  await next()
  const ms = Date.now() - start
  console.log(`${c.req.method} ${c.req.url} - ${ms}ms`)
}

const app = new Hono()
app.use('*', logger)

app.get('/', (c) => c.text('Hello'))

app.use() 的第一个参数是路径模式,* 表示匹配所有路由。

身份认证中间件

实际项目中,许多接口需要验证用户身份。我们可以封装一个认证中间件:

import { Hono, type MiddlewareHandler } from 'hono'

// 模拟 Token 验证
const auth: MiddlewareHandler = async (c, next) => {
  const token = c.req.header('Authorization')

  if (!token || !token.startsWith('Bearer ')) {
    return c.json({ error: '缺少认证令牌' }, 401)
  }

  const tokenValue = token.slice(7)
  // 实际项目中应验证 JWT 或查询数据库
  if (tokenValue !== 'valid-token') {
    return c.json({ error: '令牌无效' }, 401)
  }

  // 将用户信息存入上下文,供后续路由使用
  c.set('userId', 'user_123')
  await next()
}

const app = new Hono()

// 公开路由,不需要认证
app.get('/public', (c) => c.json({ message: '公开信息' }))

// 需要认证的路由组
const api = new Hono()
api.use('*', auth)

api.get('/profile', (c) => {
  const userId = c.get('userId')
  return c.json({ userId, name: '张三' })
})

app.route('/api', api)
export default app

错误处理 (onError)

全局错误处理捕获所有未处理的异常,统一返回格式:

const app = new Hono()

app.onError((err, c) => {
  console.error('服务器错误:', err)
  return c.json(
    {
      error: '服务器内部错误',
      message: err.message,
    },
    500
  )
})

app.get('/crash', () => {
  throw new Error('模拟异常')
})

你还可以在中间件中针对特定场景做精细化错误处理:

app.use('*', async (c, next) => {
  try {
    await next()
  } catch (error) {
    if (error instanceof SyntaxError) {
      return c.json({ error: '请求格式错误' }, 400)
    }
    throw error // 交给 onError 处理
  }
})

参数校验 (Zod)

结合 Zod 校验库,可以在请求到达处理器之前验证参数:

import { z } from 'zod'

// 定义校验 schema
const CreateUserSchema = z.object({
  name: z.string().min(2, '姓名至少 2 个字符'),
  email: z.string().email('邮箱格式不正确'),
  age: z.number().int().positive().optional(),
})

app.post('/users', async (c) => {
  const body = await c.req.json()
  const result = CreateUserSchema.safeParse(body)

  if (!result.success) {
    return c.json(
      {
        error: '参数校验失败',
        details: result.error.issues.map((i) => ({
          field: i.path.join('.'),
          message: i.message,
        })),
      },
      400
    )
  }

  // result.data 类型已经推导为 { name: string; email: string; age?: number }
  return c.json({ created: true, data: result.data }, 201)
})

你也可以将其封装为可复用的校验中间件:

import { z, type ZodSchema } from 'zod'

function validate(schema: ZodSchema) {
  return async (c: any, next: any) => {
    const body = await c.req.json()
    const result = schema.safeParse(body)
    if (!result.success) {
      return c.json({ error: result.error.issues }, 400)
    }
    c.set('validated', result.data)
    await next()
  }
}

app.post('/users', validate(CreateUserSchema), (c) => {
  const data = c.get('validated')
  return c.json({ created: true, data })
})

CORS 配置

跨域资源共享是前后端分离架构的必备配置:

import { cors } from 'hono/cors'

const app = new Hono()

app.use(
  '/api/*',
  cors({
    origin: ['https://example.com', 'https://admin.example.com'],
    allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
    allowHeaders: ['Content-Type', 'Authorization'],
    exposeHeaders: ['X-Request-Id'],
    maxAge: 86400,
    credentials: true,
  })
)

app.get('/api/data', (c) => c.json({ data: '受 CORS 保护' }))

开发环境下可以放得更宽松:

app.use('/api/*', cors())

组合中间件

多个中间件可以链式组合,执行顺序与注册顺序一致:

app.use('*', logger)
app.use('/api/*', cors())
app.use('/api/admin/*', auth)
app.use('/api/admin/*', validate(CreateUserSchema))

不同层级的中间件可以叠加,Hono 会自动按顺序执行匹配到的所有中间件,最后执行路由处理器。

小结

本文介绍了 Hono 的路由分组、全局错误处理、Zod 校验集成以及 CORS 配置。中间件机制是 Hono 最核心的设计之一,掌握它可以让你写出结构清晰、可复用的业务逻辑。下一篇文章将介绍数据库集成和用户认证。