很多人第一次上手 Hono,会自然想把它写成“Express 风格 + 一堆控制器文件”。
但 Hono 官方最佳实践一直非常明确:能不做 RoR 式 Controller,就尽量别做。
原因不是玄学,而是类型推断会变差。
#推荐写法:路径旁边就写处理逻辑
import { Hono } from 'hono'
const app = new Hono()
app.get('/outposts/:id', (c) => {
const id = c.req.param('id')
return c.json({ id }, 200)
})这时 id 的类型推断是顺的。
#不太推荐:把 handler 提前抽成“万能控制器”
import type { Context } from 'hono'
const getOutpost = (c: Context) => {
const id = c.req.param('id') // 类型推断会变得不舒服
return c.json({ id })
}Hono 的气质不是“层层包一圈”,而是“路牌和处理动作尽量挨着”。
#模块化正确姿势:用 app.route()
当哨站变多时,不要把所有驿道都堆在一个文件里。正确拆法是子哨站实例化,再挂载。
// routes/outposts.ts
import { Hono } from 'hono'
export const outposts = new Hono()
.get('/', (c) => c.json({ items: ['north-1', 'west-2'] }, 200))
.get('/:id', (c) => c.json({ id: c.req.param('id') }, 200))// app.ts
import { Hono } from 'hono'
import { outposts } from './routes/outposts'
export const app = new Hono().route('/outposts', outposts)#一个很容易踩的坑:route() 挂载顺序
app.route('/x', childApp) 不是“引用绑定”,更像“把当时已经建好的驿道抄一份贴到总图上”。
所以这种顺序可能出问题:
const child = new Hono()
const parent = new Hono()
parent.route('/child', child)
child.get('/hello', (c) => c.text('hello'))你以为挂上了,其实总图挂的是“当时还没写完的 child”。
所以经验法则很简单:
先把子哨站画完整,再挂到总哨站。
#Hono 路由并不弱
它不只是简单的 /users/:id。
你还能写:
app.get('/patrol/:zone?', (c) => c.text('可选参数'))
app.get('/logs/:date{[0-9]+}', (c) => {
return c.text(`日志日期: ${c.req.param('date')}`)
})这说明 Hono 不是“因为轻,所以简陋”;它是“轻,但足够锋利”。