最佳实践
Hono 十分灵活,你可以按照自己的偏好编写应用。 不过,仍然有一些更值得遵循的最佳实践。
尽量不要创建“控制器”
能不写“Ruby on Rails 风格的控制器”时,就别写。
ts
// 🙁
// 类似 RoR 的控制器
const booksList = (c: Context) => {
return c.json('list books')
}
app.get('/books', booksList)问题出在类型上。例如,如果不写复杂的泛型,控制器内部无法推断路径参数。
ts
// 🙁
// 类似 RoR 的控制器
const bookPermalink = (c: Context) => {
const id = c.req.param('id') // 无法推断路径参数
return c.json(`get ${id}`)
}因此你完全没必要写 RoR 风格的控制器,直接在路径定义后编写处理函数即可。
ts
// 😃
app.get('/books/:id', (c) => {
const id = c.req.param('id') // 可以正确推断路径参数
return c.json(`get ${id}`)
})使用模式验证请求
如果需要验证请求,请使用模式验证器,而不要手写验证逻辑。
ts
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'
app.post(
'/auth',
zValidator(
'json',
z.object({
email: z.string().email(),
password: z.string().min(8),
})
),
async (c) => {
const { email, password } = c.req.valid('json')
return c.json({ email, password })
}
)在调用 app.get() 之前定义配置
这是来自 JavaScript 模块的最佳实践: 在调用函数之前先定义配置对象。
ts
const route = {
method: 'GET',
path: '/',
}
app.get(route, (c) => c.text('Hello'))不要为了分组而重复使用 app.route
只有在需要为同一路径提供多个 HTTP 方法时,才应该使用 app.route。 如果只是想划分路由分组,就会显得多此一举。
ts
const app = new Hono()
const books = app.route('/books')
books.get('/', (c) => c.json(['Hello']))
books.get('/:id', (c) => c.json({ id: c.req.param('id') }))在这种情况下就显得冗余了。直接写成下面这样能得到相同的效果。
ts
app.get('/books', (c) => c.json(['Hello']))
app.get('/books/:id', (c) => c.json({ id: c.req.param('id') }))更倾向使用 app.on 而不是 app.xxx
Hono 提供了 app.on,可以为同一路径定义多个 HTTP 方法。建议使用 app.on,而不是 app.xxx。
ts
app.on(['GET', 'POST'], '/books', (c) => {
return c.json(['Hello'])
})使用 c.req.param() 而非 c.req.param['id']
请使用函数形式的 c.req.param(),而不是访问器 c.req.param['id'],以避免触发 getter。
ts
app.get('/books/:id', (c) => {
const id = c.req.param('id')
return c.json({ id })
})用模板字符串拼接响应
拼接响应文本时,请使用模板字符串,而不是字符串相加。
ts
app.get('/hello/:name', (c) => {
const { name } = c.req.param()
return c.text(`Hello ${name}!`)
})日志
使用 console.log 和 console.error 输出日志信息。
ts
app.get('/hello', async (c) => {
const start = Date.now()
const res = await c.req.parseBody()
const end = Date.now()
console.log(`[${c.req.method}] ${c.req.path} - ${end - start}ms`)
return c.json(res)
})用 c.var 复用逻辑
如果有重复使用的逻辑,可以通过 c.set 和 c.var 进行复用。
ts
app.use('*', async (c, next) => {
c.set('database', await getDatabase())
await next()
})
app.get('/users', async (c) => {
const db = c.var.database
const users = await db.users.findMany()
return c.json(users)
})等待 Response
Response 只能读取一次。等待响应会消耗它。
ts
app.get('/', async (c) => {
const res = await fetch('https://example.com')
const json = await res.json()
return c.json(json)
})