Skip to content

最佳实践

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.logconsole.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.setc.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)
})

Released under the MIT License.