教程 hono tutorial

类型化路由

Hono 本身使用 TypeScript 编写。合理使用类型可以让路由参数、请求体和共享变量更清晰。

路由参数类型

c.req.param() 返回字符串。你需要自己处理数字转换和非法值:

ts
app.get('/users/:id', (c) => {
  const id = Number(c.req.param('id'))

  if (!Number.isInteger(id) || id <= 0) {
    return c.json({ message: 'Invalid user id' }, 400)
  }

  return c.json({ id })
})

不要把 Number() 转换藏在数据库查询里,否则错误会更晚暴露。

请求体类型

可以给 c.req.json() 标注类型:

ts
type CreatePostInput = {
  title: string
  content: string
}

app.post('/posts', async (c) => {
  const input = await c.req.json<CreatePostInput>()

  return c.json({
    id: crypto.randomUUID(),
    title: input.title,
    content: input.content,
  })
})

这只是 TypeScript 编译期提示,不会自动验证运行时数据。公开 API 仍然应该使用验证器。

Variables 类型

中间件通过 c.set() 传递数据时,可以声明 Variables:

ts
import { Hono } from 'hono'

type Variables = {
  requestId: string
  user: {
    id: string
    role: 'admin' | 'user'
  }
}

const app = new Hono<{ Variables: Variables }>()

app.use(async (c, next) => {
  c.set('requestId', crypto.randomUUID())
  c.set('user', { id: 'u_1', role: 'admin' })
  await next()
})

app.get('/me', (c) => {
  const user = c.get('user')
  return c.json(user)
})

这样 c.get('user') 会得到明确类型。

子路由类型

拆分路由时可以导出子应用:

ts
// routes/posts.ts
import { Hono } from 'hono'

export const posts = new Hono()
  .get('/', (c) => c.json([]))
  .post('/', async (c) => {
    const body = await c.req.json<{ title: string }>()
    return c.json({ id: 1, title: body.title }, 201)
  })

入口文件:

ts
import { Hono } from 'hono'
import { posts } from './routes/posts'

const app = new Hono()

const routes = app.route('/posts', posts)

export type AppType = typeof routes
export default app

AppType 后续可以提供给 Hono Client,用于 RPC 类型推导。

类型与验证的边界

TypeScript 只能约束你写下的代码,不能保证客户端传来的 JSON 一定符合类型。建议把路由输入分成两层:

  • TypeScript 类型:让编辑器和编译器帮助开发。
  • 运行时验证:检查真实请求数据,返回可靠错误。

在 Hono 中,常见组合是 @hono/zod-validator 加 Zod schema。