Appearance
最佳实践
🌐 Best Practices
Hono 非常灵活。你可以随意编写你的应用。 然而,有一些最佳实践更适合遵循。
🌐 Hono is very flexible. You can write your app as you like. However, there are best practices that are better to follow.
尽量不要创建“控制器”
🌐 Don't make "Controllers" when possible
在可能的情况下,你不应该创建“类似 Ruby on Rails 的控制器”。
🌐 When possible, you should not create "Ruby on Rails-like Controllers".
ts
// 🙁
// A RoR-like Controller
const booksList = (c: Context) => {
return c.json('list books')
}
app.get('/books', booksList)这个问题与类型相关。例如,如果不编写复杂的泛型,Controller 中无法推断路径参数。
🌐 The issue is related to types. For example, the path parameter cannot be inferred in the Controller without writing complex generics.
ts
// 🙁
// A RoR-like Controller
const bookPermalink = (c: Context) => {
const id = c.req.param('id') // Can't infer the path param
return c.json(`get ${id}`)
}因此,你不需要创建类似 RoR 的控制器,而应该在路径定义后直接编写处理程序。
🌐 Therefore, you don't need to create RoR-like controllers and should write handlers directly after path definitions.
ts
// 😃
app.get('/books/:id', (c) => {
const id = c.req.param('id') // Can infer the path param
return c.json(`get ${id}`)
})factory.createHandlers() 在 hono/factory
🌐 factory.createHandlers() in hono/factory
如果你仍然想创建一个类似 RoR 的控制器,请在 hono/factory 中使用 factory.createHandlers()。如果你使用这个,类型推断将能够正确工作。
🌐 If you still want to create a RoR-like Controller, use factory.createHandlers() in hono/factory. If you use this, type inference will work correctly.
ts
import { createFactory } from 'hono/factory'
import { logger } from 'hono/logger'
// ...
// 😃
const factory = createFactory()
const middleware = factory.createMiddleware(async (c, next) => {
c.set('foo', 'bar')
await next()
})
const handlers = factory.createHandlers(logger(), middleware, (c) => {
return c.json(c.var.foo)
})
app.get('/api', ...handlers)构建更大的应用
🌐 Building a larger application
使用 app.route() 构建更大型的应用,而无需创建类似“Ruby on Rails”的控制器。
🌐 Use app.route() to build a larger application without creating "Ruby on Rails-like Controllers".
如果你的应用有 /authors 和 /books 端点,并且你希望将文件从 index.ts 分离,请创建 authors.ts 和 books.ts。
🌐 If your application has /authors and /books endpoints and you wish to separate files from index.ts, create authors.ts and books.ts.
ts
// authors.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.json('list authors'))
app.post('/', (c) => c.json('create an author', 201))
app.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
export default appts
// books.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.json('list books'))
app.post('/', (c) => c.json('create a book', 201))
app.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
export default app然后,导入它们并使用 app.route() 挂载到路径 /authors 和 /books。
🌐 Then, import them and mount on the paths /authors and /books with app.route().
ts
// index.ts
import { Hono } from 'hono'
import authors from './authors'
import books from './books'
const app = new Hono()
// 😃
app.route('/authors', authors)
app.route('/books', books)
export default app如果要使用 RPC 功能
🌐 If you want to use RPC features
上面的代码在正常使用情况下运行良好。不过,如果你想使用 RPC 功能,可以通过如下链式调用来获取正确的类型。
🌐 The code above works well for normal use cases. However, if you want to use the RPC feature, you can get the correct type by chaining as follows.
ts
// authors.ts
import { Hono } from 'hono'
const app = new Hono()
.get('/', (c) => c.json('list authors'))
.post('/', (c) => c.json('create an author', 201))
.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
export default app
export type AppType = typeof app如果你将 app 的类型传递给 hc,它将获得正确的类型。
🌐 If you pass the type of the app to hc, it will get the correct type.
ts
import type { AppType } from './authors'
import { hc } from 'hono/client'
// 😃
const client = hc<AppType>('http://localhost') // Typed correctly有关更详细的信息,请参见 RPC 页面。
🌐 For more detailed information, please see the RPC page.
HEAD 请求最佳实践
🌐 HEAD Request Best Practices
理解 Hono 的 HEAD 处理
🌐 Understanding Hono's HEAD Handling
Hono 会自动处理 HEAD 请求,将其转换为 GET 请求并去掉响应体。这一行为内置在框架的调度层中,并在路由匹配发生之前执行。
🌐 Hono automatically handles HEAD requests by converting them to GET requests and stripping the response body. This behavior is built into the framework's dispatch layer and happens before route matching occurs.
✅ 做:对 HEAD 请求使用 GET 路由
🌐 ✅ Do: Use GET Routes for HEAD Requests
typescript
// GOOD: This GET route automatically handles HEAD requests
app.get('/api/users', async (c) => {
const users = await getUsers()
c.header('X-Total-Count', users.length.toString())
return c.json(users)
})
// HEAD /api/users will return:
// - Same headers as GET (including X-Total-Count)
// - Status 200
// - No body (null)✅ 做:为 HEAD 特定逻辑使用中间件
🌐 ✅ Do: Use Middleware for HEAD-Specific Logic
typescript
// GOOD: Use middleware when HEAD needs different behavior
app.use('/api/resource', async (c, next) => {
await next()
// Add HEAD-specific headers after the handler
if (c.req.method === 'HEAD') {
c.header('X-HEAD-Processed', 'true')
// Don't compute expensive body content for HEAD
c.res = new Response(null, c.res)
}
})❌ 不要:尝试创建专用的 HEAD 处理器
🌐 ❌ Don't: Try to Create Dedicated HEAD Handlers
typescript
// BAD: This won't work as expected
app.head('/api/users', (c) => {
// This handler will NEVER be called
c.header('X-Custom', 'value')
return c.text('ignored')
})
// BAD: Using on() also won't work
app.on('HEAD', '/api/users', (c) => {
// Still converted to GET before route matching
})性能考虑
🌐 Performance Considerations
- 如果预期有大量 HEAD 请求,请避免在 GET 处理程序中进行昂贵的操作:使用中间件检测 HEAD 请求并跳过正文生成
- 缓存头的工作方式相同:HEAD 响应遵循与 GET 相同的缓存规则
- 中间件兼容性:大多数中间件可以与 HEAD 一起使用,但处理请求体的中间件(如压缩)会自动跳过 HEAD 请求
测试 HEAD 请求
🌐 Testing HEAD Requests
typescript
// Always test both GET and HEAD responses
it('handles HEAD requests correctly', async () => {
const getRes = await app.request('/api/users')
const headRes = await app.request('/api/users', { method: 'HEAD' })
expect(headRes.status).toBe(getRes.status)
expect(headRes.headers.get('X-Total-Count')).toBe(
getRes.headers.get('X-Total-Count')
)
expect(headRes.body).toBe(null)
})注释
🌐 Notes
- 自动 HEAD 转换确保 GET 和 HEAD 响应之间的标题一致
- 这种行为在所有 Hono 运行时(Cloudflare Workers、Deno、Bun、Node.js)中都是一致的
- 如果你需要为 HEAD 和 GET 完全不同的逻辑,考虑使用不同的端点,而不是试图覆盖框架的 HEAD 处理