本页还没有译文
本页 27 段还没译成简体中文,下面显示的是英文原文。Best Practices
还没翻译译自英文(hono.dev)本页 27 段都还没译
要点(模型写的)
- 直接在路径定义之后编写处理程序,而不是像 Ruby on Rails 那样使用 Controllers,这样无需复杂泛型即可推断路径参数。
- 当你仍想要类似 RoR 的 Controllers 时,请在 hono/factory 中使用 factory.createHandlers(),这样类型推断就能正常工作。
- 通过使用 app.route() 挂载子应用来构建更大的应用,而不是创建类似 RoR 的 Controllers。
- Hono 会在路由匹配发生之前自动处理 HEAD 请求,将其转换为 GET 请求并剥离响应体。
- 使用 app.head() 或 app.on('HEAD') 创建的专用 HEAD 处理程序永远不会被调用,因为请求在路由匹配之前就被转换为 GET。
模型写的
背景缺口主张方法结果分析局限
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.Hono is very flexible. However, there are best practices that are better to follow.Hono 很灵活,但有些最佳实践更好遵循 灵活性允许任意写法,但本页推荐能避免类型与结构问题的约定Hono 很灵活,但有些最佳实践更好遵循最佳实践
Don't make "Controllers" when possible
避免 RoR 式控制器,直接在路径定义后写处理函数
When possible, you should not create "Ruby on Rails-like Controllers".When possible, you should not create "Ruby on Rails-like Controllers".应尽量避免创建 Ruby on Rails 式控制器 后续说明这类控制器会破坏路径参数的类型推断应尽量避免创建 Ruby on Rails 式控制器Rails 式控制器
// 🙁
// A RoR-like Controller
const booksList = (c: Context) => {
return c.json('list books')
}
app.get('/books', booksList)The issue is related to types. For example, the path parameter cannot be inferred in the Controller without writing complex generics.The issue is related to types. For example, the path parameter cannot be inferred in the Controller without writing complex generics.控制器中路径参数需复杂泛型才能推断 仅有 Context 类型而缺少路由路径,c.req.param 无法得到具体类型控制器中路径参数需复杂泛型才能推断路径参数
// 🙁
// 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}`)
}Therefore, you don't need to create RoR-like controllers and should write handlers directly after path definitions.Therefore, you don't need to create RoR-like controllers and should write handlers directly after path definitions.直接在路径定义后写处理函数,而非 RoR 式控制器 内联处理函数让 TypeScript 从路由字符串自动推断路径参数直接在路径定义后写处理函数,而非 RoR 式控制器处理函数
// 😃
app.get('/books/:id', (c) => {
const id = c.req.param('id') // Can infer the path param
return c.json(`get ${id}`)
})factory.createHandlers() in hono/factory
使用 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.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.hono/factory 的 factory.createHandlers() 为控制器保留类型推断 它把路由上下文贯穿中间件和处理函数,使路径参数保持类型hono/factory 的 factory.createHandlers() 为控制器保留类型推断factory.createHandlers()
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() 把大型应用拆分为挂载的文件
Use app.route() to build a larger application without creating "Ruby on Rails-like Controllers".Use app.route() to build a larger application without creating "Ruby on Rails-like Controllers".用 app.route() 构建大型应用,无需 RoR 式控制器 挂载子应用让各文件处理函数保持内联,路径类型仍可推断用 app.route() 构建大型应用,无需 RoR 式控制器app.route()
If your application has /authors and /books endpoints and you wish to separate files from index.ts, create authors.ts and 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.把 /authors 和 /books 分别放到 authors.ts 与 books.ts 每个文件导出各自的 Hono 实例,包含该资源的全部路由把 /authors 和 /books 分别放到 authors.ts 与 books.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 app// 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 appThen, import them and mount on the paths /authors and /books with app.route().Then, import them and mount on the paths /authors and /books with app.route().导入子应用并用 app.route() 挂载到对应路径 index.ts 保持精简,各资源各自维护路由定义导入子应用并用 app.route() 挂载到对应路径app.route()
// 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 appIf you want to use RPC features
链式定义路由并导出 AppType,使 hc 获得正确 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.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.链式定义路由,从导出的 AppType 获得正确 RPC 类型 链式调用在 typeof app 中保留完整路由结构,RPC 客户端需要它链式定义路由,从导出的 AppType 获得正确 RPC 类型RPC 类型
// 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 appIf you pass the type of the app to hc, it will get the correct type.If you pass the type of the app to hc, it will get the correct type.将 AppType 传给 hc,客户端即获得正确类型 客户端读取导出的路由结构推断请求与响应类型将 AppType 传给 hc,客户端即获得正确类型hc
import type { AppType } from './authors'
import { hc } from 'hono/client'
// 😃
const client = hc<AppType>('http://localhost') // Typed correctlyFor more detailed information, please see the RPC page.
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.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.Hono 把 HEAD 请求转为 GET 并去除响应体 该行为在路由匹配前的分发层完成,处理函数看到的是 GETHono 把 HEAD 请求转为 GET 并去除响应体HEAD 请求
✅ Do: Use GET Routes for HEAD Requests
// 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)✅ Do: Use Middleware for HEAD-Specific Logic
// 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)
}
})❌ Don't: Try to Create Dedicated HEAD Handlers
// 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 请求应跳过昂贵的响应体生成,依赖自动跳过
- Avoid expensive operations in GET handlers if you expect many HEAD requests: Use middleware to detect HEAD and skip body generationAvoid expensive operations in GET handlers if you expect many HEAD requests: Use middleware to detect HEAD and skip body generation预计大量 HEAD 请求时,避免 GET 处理函数做昂贵运算 HEAD 仍会执行 GET 处理函数,生成用不到的响应体会浪费计算预计大量 HEAD 请求时,避免 GET 处理函数做昂贵运算GET 处理函数
- Cache headers work identically: HEAD responses respect the same caching rules as GETCache headers work identically: HEAD responses respect the same caching rules as GETHEAD 响应遵循与 GET 相同的缓存规则 HEAD 经由 GET 提供响应,因此相同的缓存响应头生效HEAD 响应遵循与 GET 相同的缓存规则缓存规则
- Middleware compatibility: Most middleware works with HEAD, but body-processing middleware (like compression) automatically skips HEAD requestsMiddleware compatibility: Most middleware works with HEAD, but body-processing middleware (like compression) automatically skips HEAD requests压缩等处理响应体的中间件会自动跳过 HEAD 请求 跳过可避免压缩 HEAD 响应本就不携带的响应体压缩等处理响应体的中间件会自动跳过 HEAD 请求中间件兼容
Testing HEAD Requests
// 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 转换在所有运行时保持响应头一致
- The automatic HEAD conversion ensures consistent headers between GET and HEAD responsesThe automatic HEAD conversion ensures consistent headers between GET and HEAD responses自动 HEAD 转换让 GET 与 HEAD 的响应头保持一致 两种方法执行同一处理函数,因此响应头值相同自动 HEAD 转换让 GET 与 HEAD 的响应头保持一致HEAD 转换
- This behavior is consistent across all Hono runtimes (Cloudflare Workers, Deno, Bun, Node.js)This behavior is consistent across all Hono runtimes (Cloudflare Workers, Deno, Bun, Node.js)HEAD 行为在 Cloudflare Workers、Deno、Bun 和 Node.js 上一致 实现转换的分发层与运行时无关HEAD 行为在 Cloudflare Workers、Deno、Bun 和 Node.js 上一致HEAD 行为
- If you need completely different logic for HEAD vs GET, consider using different endpoints rather than trying to override the framework's HEAD handlingIf you need completely different logic for HEAD vs GET, consider using different endpoints rather than trying to override the framework's HEAD handling需要完全不同的 HEAD 逻辑时,用独立端点而非覆盖框架行为 框架在路由前转换 HEAD,专用 HEAD 处理函数永远不会被调用需要完全不同的 HEAD 逻辑时,用独立端点而非覆盖框架行为HEAD 逻辑