hono-routing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHono Routing & Middleware
Hono 路由与中间件
Status: Production Ready ✅
Last Updated: 2026-01-20
Dependencies: None (framework-agnostic)
Latest Versions: hono@4.11.4, zod@4.3.5, valibot@1.2.0, @hono/zod-validator@0.7.6, @hono/valibot-validator@0.6.1
状态: 已就绪可用于生产环境 ✅
最后更新时间: 2026-01-20
依赖项: 无(与框架无关)
最新版本: hono@4.11.4, zod@4.3.5, valibot@1.2.0, @hono/zod-validator@0.7.6, @hono/valibot-validator@0.6.1
Quick Start (15 Minutes)
快速入门(15分钟)
1. Install Hono
1. 安装Hono
bash
npm install hono@4.11.4Why Hono:
- Fast: Built on Web Standards, runs on any JavaScript runtime
- Lightweight: ~10KB, no dependencies
- Type-safe: Full TypeScript support with type inference
- Flexible: Works on Cloudflare Workers, Deno, Bun, Node.js, Vercel
bash
npm install hono@4.11.4选择Hono的原因:
- 快速:基于Web标准构建,可在任何JavaScript运行时运行
- 轻量:约10KB,无依赖
- 类型安全:完整的TypeScript支持,自带类型推断
- 灵活:适用于Cloudflare Workers、Deno、Bun、Node.js、Vercel
2. Create Basic App
2. 创建基础应用
typescript
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => {
return c.json({ message: 'Hello Hono!' })
})
export default appCRITICAL:
- Use ,
c.json(),c.text()for responsesc.html() - Return the response (don't use like Express)
res.send() - Export app for runtime (Cloudflare Workers, Deno, Bun, Node.js)
typescript
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => {
return c.json({ message: 'Hello Hono!' })
})
export default app重要提示:
- 使用、
c.json()、c.text()返回响应c.html() - 必须返回响应(不要像Express那样使用)
res.send() - 导出app以适配运行时(Cloudflare Workers、Deno、Bun、Node.js)
3. Add Request Validation
3. 添加请求验证
bash
npm install zod@4.3.5 @hono/zod-validator@0.7.6typescript
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const schema = z.object({
name: z.string(),
age: z.number(),
})
app.post('/user', zValidator('json', schema), (c) => {
const data = c.req.valid('json')
return c.json({ success: true, data })
})Why Validation:
- Type-safe request data
- Automatic error responses
- Runtime validation, not just TypeScript
bash
npm install zod@4.3.5 @hono/zod-validator@0.7.6typescript
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const schema = z.object({
name: z.string(),
age: z.number(),
})
app.post('/user', zValidator('json', schema), (c) => {
const data = c.req.valid('json')
return c.json({ success: true, data })
})验证的作用:
- 确保请求数据的类型安全
- 自动返回错误响应
- 运行时验证,而非仅依赖TypeScript
The 4-Part Hono Mastery Guide
Hono 精通指南四部分
Part 1: Routing Patterns
第一部分:路由模式
Route Parameters
路由参数
typescript
// Single parameter
app.get('/users/:id', (c) => {
const id = c.req.param('id')
return c.json({ userId: id })
})
// Multiple parameters
app.get('/posts/:postId/comments/:commentId', (c) => {
const { postId, commentId } = c.req.param()
return c.json({ postId, commentId })
})
// Optional parameters (using wildcards)
app.get('/files/*', (c) => {
const path = c.req.param('*')
return c.json({ filePath: path })
})CRITICAL:
- returns single parameter
c.req.param('name') - returns all parameters as object
c.req.param() - Parameters are always strings (cast to number if needed)
typescript
// Single parameter
app.get('/users/:id', (c) => {
const id = c.req.param('id')
return c.json({ userId: id })
})
// Multiple parameters
app.get('/posts/:postId/comments/:commentId', (c) => {
const { postId, commentId } = c.req.param()
return c.json({ postId, commentId })
})
// Optional parameters (using wildcards)
app.get('/files/*', (c) => {
const path = c.req.param('*')
return c.json({ filePath: path })
})重要提示:
- 返回单个参数
c.req.param('name') - 返回所有参数组成的对象
c.req.param() - 参数始终为字符串类型(如需数字需自行转换)
Route Parameter Regex Constraints
路由参数正则约束
Use regex patterns in routes to restrict parameter matching at the routing level:
typescript
// Only matches numeric IDs
app.get('/users/:id{[0-9]+}', (c) => {
const id = c.req.param('id') // Guaranteed to be digits
return c.json({ userId: id })
})
// Only matches UUIDs
app.get('/posts/:id{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}', (c) => {
const id = c.req.param('id') // Guaranteed to be UUID format
return c.json({ postId: id })
})Benefits:
- Early validation at routing level
- Prevents invalid requests from reaching handlers
- Self-documenting route constraints
在路由中使用正则表达式,在路由层面限制参数匹配规则:
typescript
// Only matches numeric IDs
app.get('/users/:id{[0-9]+}', (c) => {
const id = c.req.param('id') // Guaranteed to be digits
return c.json({ userId: id })
})
// Only matches UUIDs
app.get('/posts/:id{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}', (c) => {
const id = c.req.param('id') // Guaranteed to be UUID format
return c.json({ postId: id })
})优势:
- 在路由层面提前验证
- 阻止无效请求到达处理函数
- 路由约束具备自文档性
Query Parameters
查询参数
typescript
app.get('/search', (c) => {
// Single query param
const q = c.req.query('q')
// Multiple query params
const { page, limit } = c.req.query()
// Query param array (e.g., ?tag=js&tag=ts)
const tags = c.req.queries('tag')
return c.json({ q, page, limit, tags })
})Best Practice:
- Use validation for query params (see Part 4)
- Provide defaults for optional params
- Parse numbers/booleans from query strings
typescript
app.get('/search', (c) => {
// Single query param
const q = c.req.query('q')
// Multiple query params
const { page, limit } = c.req.query()
// Query param array (e.g., ?tag=js&tag=ts)
const tags = c.req.queries('tag')
return c.json({ q, page, limit, tags })
})最佳实践:
- 对查询参数使用验证(见第四部分)
- 为可选参数提供默认值
- 将查询字符串解析为数字/布尔值
Route Grouping (Sub-apps)
路由分组(子应用)
typescript
// Create sub-app
const api = new Hono()
api.get('/users', (c) => c.json({ users: [] }))
api.get('/posts', (c) => c.json({ posts: [] }))
// Mount sub-app
const app = new Hono()
app.route('/api', api)
// Result: /api/users, /api/postsWhy Group Routes:
- Organize large applications
- Share middleware for specific routes
- Better code structure and maintainability
typescript
// Create sub-app
const api = new Hono()
api.get('/users', (c) => c.json({ users: [] }))
api.get('/posts', (c) => c.json({ posts: [] }))
// Mount sub-app
const app = new Hono()
app.route('/api', api)
// Result: /api/users, /api/posts路由分组的原因:
- 组织大型应用的代码结构
- 为特定路由共享中间件
- 提升代码的可维护性
Part 2: Middleware & Validation
第二部分:中间件与验证
CRITICAL Middleware Rule:
- Always call in middleware to continue the chain
await next() - Return early (without calling ) to prevent handler execution
next() - Check AFTER
c.errorfor error handlingnext()
typescript
app.use('/admin/*', async (c, next) => {
const token = c.req.header('Authorization')
if (!token) return c.json({ error: 'Unauthorized' }, 401)
await next() // Required!
})中间件核心规则:
- 必须调用以继续中间件链的执行
await next() - 提前返回(不调用)可阻止处理函数执行
next() - 在之后检查
next()以处理错误c.error
typescript
app.use('/admin/*', async (c, next) => {
const token = c.req.header('Authorization')
if (!token) return c.json({ error: 'Unauthorized' }, 401)
await next() // Required!
})Built-in Middleware
内置中间件
typescript
import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'
import { prettyJSON } from 'hono/pretty-json'
import { compress } from 'hono/compress'
import { cache } from 'hono/cache'
const app = new Hono()
// Request logging
app.use('*', logger())
// CORS
app.use('/api/*', cors({
origin: 'https://example.com',
allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
allowHeaders: ['Content-Type', 'Authorization'],
}))
// Pretty JSON (dev only)
app.use('*', prettyJSON())
// Compression (gzip/deflate)
app.use('*', compress())
// Cache responses
app.use(
'/static/*',
cache({
cacheName: 'my-app',
cacheControl: 'max-age=3600',
})
)Custom Cache Middleware Pattern:
When implementing custom cache middleware for Node.js (or other non-Cloudflare runtimes), you must clone responses before storing them in cache:
typescript
const cache = new Map<string, Response>()
const customCache = async (c, next) => {
const key = c.req.url
// Check cache
const cached = cache.get(key)
if (cached) {
return cached.clone() // Clone when returning from cache
}
// Execute handler
await next()
// Store in cache (must clone!)
cache.set(key, c.res.clone()) // ✅ Clone before storing
}
app.use('*', customCache)Why Cloning is Required:
Response bodies are readable streams that can only be consumed once. Cloning creates a new response with a fresh stream.
**Built-in Middleware Reference**: See `references/middleware-catalog.md`typescript
import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'
import { prettyJSON } from 'hono/pretty-json'
import { compress } from 'hono/compress'
import { cache } from 'hono/cache'
const app = new Hono()
// Request logging
app.use('*', logger())
// CORS
app.use('/api/*', cors({
origin: 'https://example.com',
allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
allowHeaders: ['Content-Type', 'Authorization'],
}))
// Pretty JSON (dev only)
app.use('*', prettyJSON())
// Compression (gzip/deflate)
app.use('*', compress())
// Cache responses
app.use(
'/static/*',
cache({
cacheName: 'my-app',
cacheControl: 'max-age=3600',
})
)自定义缓存中间件模式:
在Node.js(或其他非Cloudflare运行时)中实现自定义缓存中间件时,必须在存储到缓存前克隆响应:
typescript
const cache = new Map<string, Response>()
const customCache = async (c, next) => {
const key = c.req.url
// Check cache
const cached = cache.get(key)
if (cached) {
return cached.clone() // Clone when returning from cache
}
// Execute handler
await next()
// Store in cache (must clone!)
cache.set(key, c.res.clone()) // ✅ Clone before storing
}
app.use('*', customCache)为什么需要克隆:
响应体是只能被消费一次的可读流。克隆操作会创建一个带有新流的响应实例。
内置中间件参考:详见
references/middleware-catalog.mdStreaming Helpers (SSE, AI Responses)
流处理工具(SSE、AI响应)
typescript
import { Hono } from 'hono'
import { stream, streamText, streamSSE } from 'hono/streaming'
const app = new Hono()
// Binary streaming
app.get('/download', (c) => {
return stream(c, async (stream) => {
await stream.write(new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]))
await stream.pipe(readableStream)
})
})
// Text streaming (AI responses)
app.get('/ai', (c) => {
return streamText(c, async (stream) => {
for await (const chunk of aiResponse) {
await stream.write(chunk)
await stream.sleep(50) // Rate limit if needed
}
})
})
// Server-Sent Events (real-time updates)
app.get('/sse', (c) => {
return streamSSE(c, async (stream) => {
let id = 0
while (true) {
await stream.writeSSE({
data: JSON.stringify({ time: Date.now() }),
event: 'update',
id: String(id++),
})
await stream.sleep(1000)
}
})
})Use Cases:
- - Binary files, video, audio
stream() - - AI chat responses, typewriter effects
streamText() - - Real-time notifications, live feeds
streamSSE()
typescript
import { Hono } from 'hono'
import { stream, streamText, streamSSE } from 'hono/streaming'
const app = new Hono()
// Binary streaming
app.get('/download', (c) => {
return stream(c, async (stream) => {
await stream.write(new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]))
await stream.pipe(readableStream)
})
})
// Text streaming (AI responses)
app.get('/ai', (c) => {
return streamText(c, async (stream) => {
for await (const chunk of aiResponse) {
await stream.write(chunk)
await stream.sleep(50) // Rate limit if needed
}
})
})
// Server-Sent Events (real-time updates)
app.get('/sse', (c) => {
return streamSSE(c, async (stream) => {
let id = 0
while (true) {
await stream.writeSSE({
data: JSON.stringify({ time: Date.now() }),
event: 'update',
id: String(id++),
})
await stream.sleep(1000)
}
})
})适用场景:
- - 二进制文件、视频、音频
stream() - - AI聊天响应、打字机效果
streamText() - - 实时通知、实时信息流
streamSSE()
WebSocket Helper
WebSocket 工具
typescript
import { Hono } from 'hono'
import { upgradeWebSocket } from 'hono/cloudflare-workers' // Platform-specific!
const app = new Hono()
app.get('/ws', upgradeWebSocket((c) => ({
onMessage(event, ws) {
console.log(`Message: ${event.data}`)
ws.send(`Echo: ${event.data}`)
},
onClose: () => console.log('Closed'),
onError: (event) => console.error('Error:', event),
// onOpen is NOT supported on Cloudflare Workers!
})))
export default app⚠️ Cloudflare Workers WebSocket Caveats:
- Import from (not
hono/cloudflare-workers)hono/ws - callback is NOT supported (Cloudflare limitation)
onOpen - CORS/header-modifying middleware conflicts with WebSocket routes
- Use route grouping to exclude WebSocket routes from CORS:
typescript
const api = new Hono()
api.use('*', cors()) // CORS for API only
app.route('/api', api)
app.get('/ws', upgradeWebSocket(...)) // No CORS on WebSockettypescript
import { Hono } from 'hono'
import { upgradeWebSocket } from 'hono/cloudflare-workers' // Platform-specific!
const app = new Hono()
app.get('/ws', upgradeWebSocket((c) => ({
onMessage(event, ws) {
console.log(`Message: ${event.data}`)
ws.send(`Echo: ${event.data}`)
},
onClose: () => console.log('Closed'),
onError: (event) => console.error('Error:', event),
// onOpen is NOT supported on Cloudflare Workers!
})))
export default app⚠️ Cloudflare Workers WebSocket 注意事项:
- 从导入(而非
hono/cloudflare-workers)hono/ws - 回调不被支持(Cloudflare限制)
onOpen - 修改CORS/标头的中间件会与WebSocket路由冲突
- 使用路由分组,将WebSocket路由排除在CORS之外:
typescript
const api = new Hono()
api.use('*', cors()) // CORS for API only
app.route('/api', api)
app.get('/ws', upgradeWebSocket(...)) // No CORS on WebSocketSecurity Middleware
安全中间件
typescript
import { Hono } from 'hono'
import { secureHeaders } from 'hono/secure-headers'
import { csrf } from 'hono/csrf'
const app = new Hono()
// Security headers (X-Frame-Options, CSP, HSTS, etc.)
app.use('*', secureHeaders({
xFrameOptions: 'DENY',
xXssProtection: '1; mode=block',
contentSecurityPolicy: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
},
}))
// CSRF protection (validates Origin header)
app.use('/api/*', csrf({
origin: ['https://example.com', 'https://admin.example.com'],
}))Security Middleware Options:
| Middleware | Purpose |
|---|---|
| X-Frame-Options, CSP, HSTS, XSS protection |
| CSRF via Origin/Sec-Fetch-Site validation |
| Bearer token authentication |
| HTTP Basic authentication |
| IP allowlist/blocklist |
typescript
import { Hono } from 'hono'
import { secureHeaders } from 'hono/secure-headers'
import { csrf } from 'hono/csrf'
const app = new Hono()
// Security headers (X-Frame-Options, CSP, HSTS, etc.)
app.use('*', secureHeaders({
xFrameOptions: 'DENY',
xXssProtection: '1; mode=block',
contentSecurityPolicy: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
},
}))
// CSRF protection (validates Origin header)
app.use('/api/*', csrf({
origin: ['https://example.com', 'https://admin.example.com'],
}))安全中间件选项:
| 中间件 | 用途 |
|---|---|
| 提供X-Frame-Options、CSP、HSTS、XSS防护等安全标头 |
| 通过Origin/Sec-Fetch-Site验证防范CSRF攻击 |
| Bearer令牌身份验证 |
| HTTP基础身份验证 |
| IP白名单/黑名单限制 |
Combine Middleware
组合中间件
Compose middleware with conditional logic:
typescript
import { Hono } from 'hono'
import { some, every, except } from 'hono/combine'
import { bearerAuth } from 'hono/bearer-auth'
import { ipRestriction } from 'hono/ip-restriction'
const app = new Hono()
// some: ANY middleware must pass (OR logic)
app.use('/admin/*', some(
bearerAuth({ token: 'admin-token' }),
ipRestriction({ allowList: ['10.0.0.0/8'] }),
))
// every: ALL middleware must pass (AND logic)
app.use('/secure/*', every(
bearerAuth({ token: 'secret' }),
ipRestriction({ allowList: ['192.168.1.0/24'] }),
))
// except: Skip middleware for certain paths
app.use('*', except(
['/health', '/metrics'],
logger(),
))通过条件逻辑组合中间件:
typescript
import { Hono } from 'hono'
import { some, every, except } from 'hono/combine'
import { bearerAuth } from 'hono/bearer-auth'
import { ipRestriction } from 'hono/ip-restriction'
const app = new Hono()
// some: ANY middleware must pass (OR logic)
app.use('/admin/*', some(
bearerAuth({ token: 'admin-token' }),
ipRestriction({ allowList: ['10.0.0.0/8'] }),
))
// every: ALL middleware must pass (AND logic)
app.use('/secure/*', every(
bearerAuth({ token: 'secret' }),
ipRestriction({ allowList: ['192.168.1.0/24'] }),
))
// except: Skip middleware for certain paths
app.use('*', except(
['/health', '/metrics'],
logger(),
))Part 3: Type-Safe Context Extension
第三部分:类型安全上下文扩展
Using c.set() and c.get()
使用c.set()和c.get()
typescript
import { Hono } from 'hono'
type Bindings = {
DATABASE_URL: string
}
type Variables = {
user: {
id: number
name: string
}
requestId: string
}
const app = new Hono<{ Bindings: Bindings; Variables: Variables }>()
// Middleware sets variables
app.use('*', async (c, next) => {
c.set('requestId', crypto.randomUUID())
await next()
})
app.use('/api/*', async (c, next) => {
c.set('user', { id: 1, name: 'Alice' })
await next()
})
// Route accesses variables
app.get('/api/profile', (c) => {
const user = c.get('user') // Type-safe!
const requestId = c.get('requestId') // Type-safe!
return c.json({ user, requestId })
})CRITICAL:
- Define type for type-safe
Variablesc.get() - Define type for environment variables (Cloudflare Workers)
Bindings - in middleware,
c.set()in handlersc.get()
typescript
import { Hono } from 'hono'
type Bindings = {
DATABASE_URL: string
}
type Variables = {
user: {
id: number
name: string
}
requestId: string
}
const app = new Hono<{ Bindings: Bindings; Variables: Variables }>()
// Middleware sets variables
app.use('*', async (c, next) => {
c.set('requestId', crypto.randomUUID())
await next()
})
app.use('/api/*', async (c, next) => {
c.set('user', { id: 1, name: 'Alice' })
await next()
})
// Route accesses variables
app.get('/api/profile', (c) => {
const user = c.get('user') // Type-safe!
const requestId = c.get('requestId') // Type-safe!
return c.json({ user, requestId })
})重要提示:
- 定义类型以实现类型安全的
Variablesc.get() - 定义类型以适配环境变量(Cloudflare Workers)
Bindings - 在中间件中使用,在处理函数中使用
c.set()c.get()
Custom Context Extension
自定义上下文扩展
typescript
import { Hono } from 'hono'
import type { Context } from 'hono'
type Env = {
Variables: {
logger: {
info: (message: string) => void
error: (message: string) => void
}
}
}
const app = new Hono<Env>()
// Create logger middleware
app.use('*', async (c, next) => {
const logger = {
info: (msg: string) => console.log(`[INFO] ${msg}`),
error: (msg: string) => console.error(`[ERROR] ${msg}`),
}
c.set('logger', logger)
await next()
})
app.get('/', (c) => {
const logger = c.get('logger')
logger.info('Hello from route')
return c.json({ message: 'Hello' })
})Advanced Pattern: See
templates/context-extension.tstypescript
import { Hono } from 'hono'
import type { Context } from 'hono'
type Env = {
Variables: {
logger: {
info: (message: string) => void
error: (message: string) => void
}
}
}
const app = new Hono<Env>()
// Create logger middleware
app.use('*', async (c, next) => {
const logger = {
info: (msg: string) => console.log(`[INFO] ${msg}`),
error: (msg: string) => console.error(`[ERROR] ${msg}`),
}
c.set('logger', logger)
await next()
})
app.get('/', (c) => {
const logger = c.get('logger')
logger.info('Hello from route')
return c.json({ message: 'Hello' })
})高级模式:详见
templates/context-extension.tsPart 4: Request Validation
第四部分:请求验证
Validation with Zod
使用Zod进行验证
bash
npm install zod@4.3.5 @hono/zod-validator@0.7.6typescript
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
// Define schema
const userSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(18).optional(),
})
// Validate JSON body
app.post('/users', zValidator('json', userSchema), (c) => {
const data = c.req.valid('json') // Type-safe!
return c.json({ success: true, data })
})
// Validate query params
const searchSchema = z.object({
q: z.string(),
page: z.string().transform((val) => parseInt(val, 10)),
limit: z.string().transform((val) => parseInt(val, 10)).optional(),
})
app.get('/search', zValidator('query', searchSchema), (c) => {
const { q, page, limit } = c.req.valid('query')
return c.json({ q, page, limit })
})
// Validate route params
const idSchema = z.object({
id: z.string().uuid(),
})
app.get('/users/:id', zValidator('param', idSchema), (c) => {
const { id } = c.req.valid('param')
return c.json({ userId: id })
})
// Validate headers
const headerSchema = z.object({
'authorization': z.string().startsWith('Bearer '),
'content-type': z.string(),
})
app.post('/auth', zValidator('header', headerSchema), (c) => {
const headers = c.req.valid('header')
return c.json({ authenticated: true })
})CRITICAL:
- Always use after validation (type-safe)
c.req.valid() - Validation targets: ,
json,query,param,header,formcookie - Use to convert strings to numbers/dates
z.transform() - Validation errors return 400 automatically
⚠️ CRITICAL: Validation Must Be Handler-Specific
For validated types to be inferred correctly, validation middleware must be added in the handler, not via :
app.use()typescript
// ❌ WRONG - Type inference breaks
app.use('/users', zValidator('json', userSchema))
app.post('/users', (c) => {
const data = c.req.valid('json') // TS Error: Type 'never'
return c.json({ data })
})
// ✅ CORRECT - Validation in handler
app.post('/users', zValidator('json', userSchema), (c) => {
const data = c.req.valid('json') // Type-safe!
return c.json({ data })
})Why It Happens:
Hono's type mapping merges validation results using generics. When validators are applied via , the type system cannot track which routes have which validation schemas, causing the generic to collapse to .
Inputapp.use()Inputneverbash
npm install zod@4.3.5 @hono/zod-validator@0.7.6typescript
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
// Define schema
const userSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(18).optional(),
})
// Validate JSON body
app.post('/users', zValidator('json', userSchema), (c) => {
const data = c.req.valid('json') // Type-safe!
return c.json({ success: true, data })
})
// Validate query params
const searchSchema = z.object({
q: z.string(),
page: z.string().transform((val) => parseInt(val, 10)),
limit: z.string().transform((val) => parseInt(val, 10)).optional(),
})
app.get('/search', zValidator('query', searchSchema), (c) => {
const { q, page, limit } = c.req.valid('query')
return c.json({ q, page, limit })
})
// Validate route params
const idSchema = z.object({
id: z.string().uuid(),
})
app.get('/users/:id', zValidator('param', idSchema), (c) => {
const { id } = c.req.valid('param')
return c.json({ userId: id })
})
// Validate headers
const headerSchema = z.object({
'authorization': z.string().startsWith('Bearer '),
'content-type': z.string(),
})
app.post('/auth', zValidator('header', headerSchema), (c) => {
const headers = c.req.valid('header')
return c.json({ authenticated: true })
})重要提示:
- 必须在验证后使用以获取类型安全的数据
c.req.valid() - 验证目标包括:、
json、query、param、header、formcookie - 使用将字符串转换为数字/日期类型
z.transform() - 验证失败会自动返回400错误
⚠️ 关键注意:验证必须与处理函数绑定
为了正确推断验证后的类型,验证中间件必须添加在处理函数中,而非通过全局添加:
app.use()typescript
// ❌ 错误 - 类型推断失效
app.use('/users', zValidator('json', userSchema))
app.post('/users', (c) => {
const data = c.req.valid('json') // TS Error: Type 'never'
return c.json({ data })
})
// ✅ 正确 - 验证与处理函数绑定
app.post('/users', zValidator('json', userSchema), (c) => {
const data = c.req.valid('json') // Type-safe!
return c.json({ data })
})原因:
Hono的类型映射通过泛型合并验证结果。当验证器通过全局应用时,类型系统无法跟踪哪些路由使用了哪些验证模式,导致泛型退化为。
Inputapp.use()InputneverCustom Validation Hooks
自定义验证钩子
typescript
import { zValidator } from '@hono/zod-validator'
import { HTTPException } from 'hono/http-exception'
const schema = z.object({
name: z.string(),
age: z.number(),
})
// Custom error handler
app.post(
'/users',
zValidator('json', schema, (result, c) => {
if (!result.success) {
// Custom error response
return c.json(
{
error: 'Validation failed',
issues: result.error.issues,
},
400
)
}
}),
(c) => {
const data = c.req.valid('json')
return c.json({ success: true, data })
}
)
// Throw HTTPException
app.post(
'/users',
zValidator('json', schema, (result, c) => {
if (!result.success) {
throw new HTTPException(400, { cause: result.error })
}
}),
(c) => {
const data = c.req.valid('json')
return c.json({ success: true, data })
}
)Note on Zod Optional Enums:
Prior to , optional enums incorrectly resolved to strings instead of the enum type. This was fixed in v0.7.6. Ensure you're using the latest version:
@hono/zod-validator@0.7.6bash
npm install @hono/zod-validator@0.7.6typescript
import { zValidator } from '@hono/zod-validator'
import { HTTPException } from 'hono/http-exception'
const schema = z.object({
name: z.string(),
age: z.number(),
})
// Custom error handler
app.post(
'/users',
zValidator('json', schema, (result, c) => {
if (!result.success) {
// Custom error response
return c.json(
{
error: 'Validation failed',
issues: result.error.issues,
},
400
)
}
}),
(c) => {
const data = c.req.valid('json')
return c.json({ success: true, data })
}
)
// Throw HTTPException
app.post(
'/users',
zValidator('json', schema, (result, c) => {
if (!result.success) {
throw new HTTPException(400, { cause: result.error })
}
}),
(c) => {
const data = c.req.valid('json')
return c.json({ success: true, data })
}
)关于Zod可选枚举的注意事项:
在版本之前,可选枚举会错误地解析为字符串类型而非枚举类型。该问题在v0.7.6中已修复,请确保使用最新版本:
@hono/zod-validator@0.7.6bash
npm install @hono/zod-validator@0.7.6Validation with Valibot
使用Valibot进行验证
bash
npm install valibot@1.2.0 @hono/valibot-validator@0.6.1typescript
import { vValidator } from '@hono/valibot-validator'
import * as v from 'valibot'
const schema = v.object({
name: v.string(),
age: v.number(),
})
app.post('/users', vValidator('json', schema), (c) => {
const data = c.req.valid('json')
return c.json({ success: true, data })
})Zod vs Valibot: See
references/validation-libraries.mdbash
npm install valibot@1.2.0 @hono/valibot-validator@0.6.1typescript
import { vValidator } from '@hono/valibot-validator'
import * as v from 'valibot'
const schema = v.object({
name: v.string(),
age: v.number(),
})
app.post('/users', vValidator('json', schema), (c) => {
const data = c.req.valid('json')
return c.json({ success: true, data })
})Zod vs Valibot:详见
references/validation-libraries.mdValidation with Typia
使用Typia进行验证
bash
npm install typia @hono/typia-validator@0.1.2typescript
import { typiaValidator } from '@hono/typia-validator'
import typia from 'typia'
interface User {
name: string
age: number
}
const validate = typia.createValidate<User>()
app.post('/users', typiaValidator('json', validate), (c) => {
const data = c.req.valid('json')
return c.json({ success: true, data })
})Why Typia:
- Fastest validation (compile-time)
- No runtime schema definition
- AOT (Ahead-of-Time) compilation
bash
npm install typia @hono/typia-validator@0.1.2typescript
import { typiaValidator } from '@hono/typia-validator'
import typia from 'typia'
interface User {
name: string
age: number
}
const validate = typia.createValidate<User>()
app.post('/users', typiaValidator('json', validate), (c) => {
const data = c.req.valid('json')
return c.json({ success: true, data })
})选择Typia的原因:
- 最快的验证速度(编译时验证)
- 无需运行时模式定义
- AOT(提前编译)支持
Validation with ArkType
使用ArkType进行验证
bash
npm install arktype @hono/arktype-validator@2.0.1typescript
import { arktypeValidator } from '@hono/arktype-validator'
import { type } from 'arktype'
const schema = type({
name: 'string',
age: 'number',
})
app.post('/users', arktypeValidator('json', schema), (c) => {
const data = c.req.valid('json')
return c.json({ success: true, data })
})Comparison: See for detailed comparison
references/validation-libraries.mdbash
npm install arktype @hono/arktype-validator@2.0.1typescript
import { arktypeValidator } from '@hono/arktype-validator'
import { type } from 'arktype'
const schema = type({
name: 'string',
age: 'number',
})
app.post('/users', arktypeValidator('json', schema), (c) => {
const data = c.req.valid('json')
return c.json({ success: true, data })
})对比详情:详见
references/validation-libraries.mdPart 5: Typed Routes (RPC)
第五部分:类型化路由(RPC)
Why RPC?
为什么使用RPC?
Hono's RPC feature allows type-safe client/server communication without manual API type definitions. The client infers types directly from the server routes.
Hono的RPC功能允许类型安全的客户端/服务器通信,无需手动定义API类型。客户端直接从服务器路由推断类型。
Server-Side Setup
服务端设置
typescript
// app.ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const app = new Hono()
const schema = z.object({
name: z.string(),
age: z.number(),
})
// Define route and export type
const route = app.post(
'/users',
zValidator('json', schema),
(c) => {
const data = c.req.valid('json')
return c.json({ success: true, data }, 201)
}
)
// Export app type for RPC client
export type AppType = typeof route
// OR export entire app
// export type AppType = typeof app
export default appCRITICAL:
- Must use for RPC type inference
const route = app.get(...) - Export or
typeof routetypeof app - Don't use anonymous route definitions
typescript
// app.ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const app = new Hono()
const schema = z.object({
name: z.string(),
age: z.number(),
})
// Define route and export type
const route = app.post(
'/users',
zValidator('json', schema),
(c) => {
const data = c.req.valid('json')
return c.json({ success: true, data }, 201)
}
)
// Export app type for RPC client
export type AppType = typeof route
// OR export entire app
// export type AppType = typeof app
export default app重要提示:
- 必须使用模式 以支持RPC类型推断
const route = app.get(...) - 导出或
typeof routetypeof app - 不要使用匿名路由定义
Client-Side Setup
客户端设置
typescript
// client.ts
import { hc } from 'hono/client'
import type { AppType } from './app'
const client = hc<AppType>('http://localhost:8787')
// Type-safe API call
const res = await client.users.$post({
json: {
name: 'Alice',
age: 30,
},
})
// Response is typed!
const data = await res.json() // { success: boolean, data: { name: string, age: number } }Why RPC:
- ✅ Full type inference (request + response)
- ✅ No manual type definitions
- ✅ Compile-time error checking
- ✅ Auto-complete in IDE
⚠️ RPC Type Inference Limitation:
The RPC client only infers types for and responses. If an endpoint returns multiple response types (e.g., JSON and binary), none of the responses will be type-inferred:
jsontexttypescript
// ❌ Type inference fails - mixes JSON and binary
app.post('/upload', async (c) => {
const body = await c.req.body() // Binary response
if (error) {
return c.json({ error: 'Bad request' }, 400) // JSON response
}
return c.json({ success: true })
})
// ✅ Separate endpoints by response type
app.post('/upload', async (c) => {
return c.json({ success: true }) // Only JSON - types work
})
app.get('/download/:id', async (c) => {
return c.body(binaryData) // Only binary - separate endpoint
})typescript
// client.ts
import { hc } from 'hono/client'
import type { AppType } from './app'
const client = hc<AppType>('http://localhost:8787')
// Type-safe API call
const res = await client.users.$post({
json: {
name: 'Alice',
age: 30,
},
})
// Response is typed!
const data = await res.json() // { success: boolean, data: { name: string, age: number } }使用RPC的优势:
- ✅ 完整的类型推断(请求+响应)
- ✅ 无需手动定义类型
- ✅ 编译时错误检查
- ✅ IDE自动补全
⚠️ RPC类型推断限制:
RPC客户端仅能推断和类型的响应。如果端点返回多种响应类型(如JSON和二进制),所有响应的类型都将无法被推断:
jsontexttypescript
// ❌ 类型推断失效 - 混合JSON和二进制响应
app.post('/upload', async (c) => {
const body = await c.req.body() // Binary response
if (error) {
return c.json({ error: 'Bad request' }, 400) // JSON response
}
return c.json({ success: true })
})
// ✅ 按响应类型拆分端点
app.post('/upload', async (c) => {
return c.json({ success: true }) // 仅返回JSON - 类型推断正常
})
app.get('/download/:id', async (c) => {
return c.body(binaryData) // 仅返回二进制 - 单独端点
})RPC with Multiple Routes
多路由RPC
typescript
// Server
const app = new Hono()
const getUsers = app.get('/users', (c) => {
return c.json({ users: [] })
})
const createUser = app.post(
'/users',
zValidator('json', userSchema),
(c) => {
const data = c.req.valid('json')
return c.json({ success: true, data }, 201)
}
)
const getUser = app.get('/users/:id', (c) => {
const id = c.req.param('id')
return c.json({ id, name: 'Alice' })
})
// Export combined type
export type AppType = typeof getUsers | typeof createUser | typeof getUser
// Client
const client = hc<AppType>('http://localhost:8787')
// GET /users
const usersRes = await client.users.$get()
// POST /users
const createRes = await client.users.$post({
json: { name: 'Alice', age: 30 },
})
// GET /users/:id
const userRes = await client.users[':id'].$get({
param: { id: '123' },
})typescript
// Server
const app = new Hono()
const getUsers = app.get('/users', (c) => {
return c.json({ users: [] })
})
const createUser = app.post(
'/users',
zValidator('json', userSchema),
(c) => {
const data = c.req.valid('json')
return c.json({ success: true, data }, 201)
}
)
const getUser = app.get('/users/:id', (c) => {
const id = c.req.param('id')
return c.json({ id, name: 'Alice' })
})
// Export combined type
export type AppType = typeof getUsers | typeof createUser | typeof getUser
// Client
const client = hc<AppType>('http://localhost:8787')
// GET /users
const usersRes = await client.users.$get()
// POST /users
const createRes = await client.users.$post({
json: { name: 'Alice', age: 30 },
})
// GET /users/:id
const userRes = await client.users[':id'].$get({
param: { id: '123' },
})RPC Performance Optimization
RPC性能优化
Problem: Large apps with many routes cause slow type inference
Solution: Export specific route groups instead of entire app
typescript
// ❌ Slow: Export entire app
export type AppType = typeof app
// ✅ Fast: Export specific routes
const userRoutes = app.get('/users', ...).post('/users', ...)
export type UserRoutes = typeof userRoutes
const postRoutes = app.get('/posts', ...).post('/posts', ...)
export type PostRoutes = typeof postRoutes
// Client imports specific routes
import type { UserRoutes } from './app'
const userClient = hc<UserRoutes>('http://localhost:8787')Deep Dive: See
references/rpc-guide.md问题:包含大量路由的大型应用会导致类型推断速度变慢
解决方案:导出特定的路由组而非整个应用
typescript
// ❌ 速度慢:导出整个应用
export type AppType = typeof app
// ✅ 速度快:导出特定路由组
const userRoutes = app.get('/users', ...).post('/users', ...)
export type UserRoutes = typeof userRoutes
const postRoutes = app.get('/posts', ...).post('/posts', ...)
export type PostRoutes = typeof postRoutes
// Client imports specific routes
import type { UserRoutes } from './app'
const userClient = hc<UserRoutes>('http://localhost:8787')深入指南:详见
references/rpc-guide.mdPart 6: Error Handling
第六部分:错误处理
HTTPException
HTTPException
typescript
import { Hono } from 'hono'
import { HTTPException } from 'hono/http-exception'
const app = new Hono()
app.get('/users/:id', (c) => {
const id = c.req.param('id')
// Throw HTTPException for client errors
if (!id) {
throw new HTTPException(400, { message: 'ID is required' })
}
// With custom response
if (id === 'invalid') {
const res = new Response('Custom error body', { status: 400 })
throw new HTTPException(400, { res })
}
return c.json({ id })
})CRITICAL:
- Use HTTPException for expected errors (400, 401, 403, 404)
- Don't use for unexpected errors (500) - use instead
onError - HTTPException stops execution immediately
typescript
import { Hono } from 'hono'
import { HTTPException } from 'hono/http-exception'
const app = new Hono()
app.get('/users/:id', (c) => {
const id = c.req.param('id')
// Throw HTTPException for client errors
if (!id) {
throw new HTTPException(400, { message: 'ID is required' })
}
// With custom response
if (id === 'invalid') {
const res = new Response('Custom error body', { status: 400 })
throw new HTTPException(400, { res })
}
return c.json({ id })
})重要提示:
- 对客户端错误(400、401、403、404)使用HTTPException
- 不要对意外错误(500)使用HTTPException - 改用处理
onError - HTTPException会立即终止执行
Global Error Handler (onError)
全局错误处理器(onError)
typescript
import { Hono } from 'hono'
import { HTTPException } from 'hono/http-exception'
const app = new Hono()
// Custom error handler
app.onError((err, c) => {
// Handle HTTPException
if (err instanceof HTTPException) {
return err.getResponse()
}
// Handle unexpected errors
console.error('Unexpected error:', err)
return c.json(
{
error: 'Internal Server Error',
message: err.message,
},
500
)
})
app.get('/error', (c) => {
throw new Error('Something went wrong!')
})Why onError:
- Centralized error handling
- Consistent error responses
- Error logging and tracking
typescript
import { Hono } from 'hono'
import { HTTPException } from 'hono/http-exception'
const app = new Hono()
// Custom error handler
app.onError((err, c) => {
// Handle HTTPException
if (err instanceof HTTPException) {
return err.getResponse()
}
// Handle unexpected errors
console.error('Unexpected error:', err)
return c.json(
{
error: 'Internal Server Error',
message: err.message,
},
500
)
})
app.get('/error', (c) => {
throw new Error('Something went wrong!')
})使用onError的原因:
- 集中处理错误
- 统一错误响应格式
- 错误日志与追踪
Middleware Error Checking
中间件错误检查
typescript
app.use('*', async (c, next) => {
await next()
// Check for errors after handler
if (c.error) {
console.error('Error in route:', c.error)
// Send to error tracking service
}
})typescript
app.use('*', async (c, next) => {
await next()
// Check for errors after handler
if (c.error) {
console.error('Error in route:', c.error)
// Send to error tracking service
}
})Not Found Handler
404处理器
typescript
app.notFound((c) => {
return c.json({ error: 'Not Found' }, 404)
})typescript
app.notFound((c) => {
return c.json({ error: 'Not Found' }, 404)
})Critical Rules
核心规则
Always Do
必须遵守
✅ Call in middleware - Required for middleware chain execution
✅ Return Response from handlers - Use , ,
✅ Use after validation - Type-safe validated data
✅ Export route types for RPC -
✅ Throw HTTPException for client errors - 400, 401, 403, 404 errors
✅ Use for global error handling - Centralized error responses
✅ Define Variables type for c.set/c.get - Type-safe context variables
✅ Use const route = app.get(...) - Required for RPC type inference
await next()c.json()c.text()c.html()c.req.valid()export type AppType = typeof routeonError✅ 在中间件中调用 - 中间件链执行的必要条件
✅ 从处理函数返回响应 - 使用、、
✅ 验证后使用 - 获取类型安全的验证数据
✅ 为RPC导出路由类型 -
✅ 客户端错误抛出HTTPException - 400、401、403、404错误使用该方式
✅ 使用进行全局错误处理 - 集中统一的错误响应
✅ 为c.set/c.get定义Variables类型 - 类型安全的上下文变量
✅ 使用const route = app.get(...)模式 - RPC类型推断的必要条件
await next()c.json()c.text()c.html()c.req.valid()export type AppType = typeof routeonErrorNever Do
禁止操作
❌ Forget in middleware - Breaks middleware chain
❌ Use like Express - Not compatible with Hono
❌ Access request data without validation - Use validators for type safety
❌ Export entire app for large RPC - Slow type inference, export specific routes
❌ Use plain throw new Error() - Use HTTPException instead
❌ Skip onError handler - Leads to inconsistent error responses
❌ Use c.set/c.get without Variables type - Loses type safety
await next()res.send()❌ 中间件中忘记 - 会中断中间件链
❌ 像Express一样使用 - 与Hono不兼容
❌ 不验证就访问请求数据 - 使用验证器确保类型安全
❌ 大型RPC应用导出整个应用 - 类型推断速度慢,应导出特定路由
❌ 直接抛出new Error() - 改用HTTPException
❌ 跳过onError处理器 - 会导致错误响应格式不一致
❌ 不定义Variables类型就使用c.set/c.get - 丢失类型安全
await next()res.send()Known Issues Prevention
已知问题预防
This skill prevents 10 documented issues:
本技能可预防10个已记录的问题:
Issue #1: RPC Type Inference Slow
问题1:RPC类型推断速度慢
Error: IDE becomes slow with many routes (8-minute CI builds, non-existent IntelliSense)
Source: hono/docs/guides/rpc | GitHub Issue #3869
Why It Happens: Complex type instantiation from with many routes. Exacerbated by Zod methods like , , .
Prevention: Export specific route groups instead of entire app
typeof appomitextendpicktypescript
// ❌ Slow
export type AppType = typeof app
// ✅ Fast
const userRoutes = app.get(...).post(...)
export type UserRoutes = typeof userRoutesAdvanced Workaround for Large Apps (100+ routes):
- Split into monorepo libs:
typescript
// routers-auth/index.ts
export const authRouter = new Hono()
.get('/login', ...)
.post('/login', ...)
// routers-orders/index.ts
export const orderRouter = new Hono()
.get('/orders', ...)
.post('/orders', ...)
// routers-main/index.ts
const app = new Hono()
.route('/auth', authRouter)
.route('/orders', orderRouter)
export type AppType = typeof app-
Use separate build configs:
- Production: Full with
tscgeneration (for RPC client).d.ts - Development: Skip on main router, only type-check sub-routers (faster live-reload)
tsc
- Production: Full
-
Avoid Zod methods that hurt performance:
- ,
z.omit(),z.extend()- These increase language server workload by 10xz.pick() - Use interfaces instead of intersections when possible
错误表现:路由过多时IDE响应变慢(CI构建耗时8分钟,智能提示失效)
来源:hono/docs/guides/rpc | GitHub Issue #3869
原因:的复杂类型实例化导致。Zod的、、等方法会加剧该问题。
预防方案:导出特定路由组而非整个应用
typeof appomitextendpicktypescript
// ❌ 速度慢
export type AppType = typeof app
// ✅ 速度快
const userRoutes = app.get(...).post(...)
export type UserRoutes = typeof userRoutes大型应用高级解决方案(100+路由):
- 拆分为单体仓库模块:
typescript
// routers-auth/index.ts
export const authRouter = new Hono()
.get('/login', ...)
.post('/login', ...)
// routers-orders/index.ts
export const orderRouter = new Hono()
.get('/orders', ...)
.post('/orders', ...)
// routers-main/index.ts
const app = new Hono()
.route('/auth', authRouter)
.route('/orders', orderRouter)
export type AppType = typeof app-
使用单独的构建配置:
- 生产环境:使用完整生成
tsc文件(供RPC客户端使用).d.ts - 开发环境:跳过主路由的检查,仅对子路由进行类型检查(提升热重载速度)
tsc
- 生产环境:使用完整
-
避免使用影响性能的Zod方法:
- 、
z.omit()、z.extend()- 会使语言服务器负载增加10倍z.pick() - 尽可能使用接口而非交叉类型
Issue #2: Middleware Response Not Typed in RPC
问题2:中间件响应未在RPC中类型化
Error: Middleware responses (including and ) not inferred by RPC client
Source: honojs/hono#2719 | GitHub Issue #4600
Why It Happens: RPC mode doesn't infer middleware responses by default. Responses from or handlers are not included in type map.
Prevention: Export specific route types that include middleware
notFound()onError()notFound()onError()typescript
const route = app.get(
'/data',
myMiddleware,
(c) => c.json({ data: 'value' })
)
export type AppType = typeof routeSpecific Issue: notFound/onError Not Typed:
typescript
// Server
const app = new Hono()
.notFound((c) => c.json({ error: 'Not Found' }, 404))
.get('/users/:id', async (c) => {
const user = await getUser(c.req.param('id'))
if (!user) {
return c.notFound() // Type not exported to RPC client
}
return c.json({ user })
})
// Client
const client = hc<typeof app>('http://localhost:8787')
const res = await client.users[':id'].$get({ param: { id: '123' } })
if (res.status === 404) {
const error = await res.json() // Type is 'any', not { error: string }
}Partial Workaround (v4.11.0+):
Use module augmentation to customize type:
NotFoundResponsetypescript
import { Hono, TypedResponse } from 'hono'
declare module 'hono' {
interface NotFoundResponse
extends Response,
TypedResponse<{ error: string }, 404, 'json'> {}
}错误表现:中间件响应(包括和)未被RPC客户端推断
来源:honojs/hono#2719 | GitHub Issue #4600
原因:RPC模式默认不推断中间件响应。或处理器的响应未包含在类型映射中。
预防方案:导出包含中间件的特定路由类型
notFound()onError()notFound()onError()typescript
const route = app.get(
'/data',
myMiddleware,
(c) => c.json({ data: 'value' })
)
export type AppType = typeof route特定问题:notFound/onError未类型化:
typescript
// Server
const app = new Hono()
.notFound((c) => c.json({ error: 'Not Found' }, 404))
.get('/users/:id', async (c) => {
const user = await getUser(c.req.param('id'))
if (!user) {
return c.notFound() // 类型未导出到RPC客户端
}
return c.json({ user })
})
// Client
const client = hc<typeof app>('http://localhost:8787')
const res = await client.users[':id'].$get({ param: { id: '123' } })
if (res.status === 404) {
const error = await res.json() // 类型为'any',而非{ error: string }
}部分解决方案(v4.11.0+):
使用模块扩展自定义类型:
NotFoundResponsetypescript
import { Hono, TypedResponse } from 'hono'
declare module 'hono' {
interface NotFoundResponse
extends Response,
TypedResponse<{ error: string }, 404, 'json'> {}
}Issue #3: Validation Hook Confusion
问题3:验证钩子混淆
Error: Different validator libraries have different hook patterns
Source: Context7 research
Why It Happens: Each validator (@hono/zod-validator, @hono/valibot-validator, etc.) has slightly different APIs
Prevention: This skill provides consistent patterns for all validators
错误表现:不同验证库的钩子模式不同
来源:Context7研究
原因:每个验证器(@hono/zod-validator、@hono/valibot-validator等)的API略有差异
预防方案:本技能为所有验证器提供统一的使用模式
Issue #4: HTTPException Misuse
问题4:HTTPException误用
Error: Throwing plain Error instead of HTTPException
Source: Official docs
Why It Happens: Developers familiar with Express use
Prevention: Always use for client errors (400-499)
throw new Error()HTTPExceptiontypescript
// ❌ Wrong
throw new Error('Unauthorized')
// ✅ Correct
throw new HTTPException(401, { message: 'Unauthorized' })错误表现:抛出普通Error而非HTTPException
来源:官方文档
原因:熟悉Express的开发者习惯使用
预防方案:客户端错误(400-499)始终使用HTTPException
throw new Error()typescript
// ❌ 错误方式
throw new Error('Unauthorized')
// ✅ 正确方式
throw new HTTPException(401, { message: 'Unauthorized' })Issue #5: Context Type Safety Lost
问题5:上下文类型安全丢失
Error: and without type inference
Source: Official docs
Why It Happens: Not defining type in Hono generic
Prevention: Always define Variables type
c.set()c.get()Variablestypescript
type Variables = {
user: { id: number; name: string }
}
const app = new Hono<{ Variables: Variables }>()错误表现:和无类型推断
来源:官方文档
原因:未在Hono泛型中定义类型
预防方案:始终定义Variables类型
c.set()c.get()Variablestypescript
type Variables = {
user: { id: number; name: string }
}
const app = new Hono<{ Variables: Variables }>()Issue #6: Missing Error Check After Middleware
问题6:中间件后未检查错误
Error: Errors in handlers not caught
Source: Official docs
Why It Happens: Not checking after
Prevention: Check in middleware
c.errorawait next()c.errortypescript
app.use('*', async (c, next) => {
await next()
if (c.error) {
console.error('Error:', c.error)
}
})错误表现:处理函数中的错误未被捕获
来源:官方文档
原因:未在后检查
预防方案:在中间件中检查
await next()c.errorc.errortypescript
app.use('*', async (c, next) => {
await next()
if (c.error) {
console.error('Error:', c.error)
}
})Issue #7: Direct Request Access Without Validation
问题7:未验证直接访问请求
Error: Accessing or without validation
Source: Best practices
Why It Happens: Developers skip validation for speed
Prevention: Always use validators and
c.req.param()c.req.query()c.req.valid()typescript
// ❌ Wrong
const id = c.req.param('id') // string, no validation
// ✅ Correct
app.get('/users/:id', zValidator('param', idSchema), (c) => {
const { id } = c.req.valid('param') // validated UUID
})错误表现:不验证就访问或
来源:最佳实践
原因:开发者为了快速开发跳过验证
预防方案:始终使用验证器和
c.req.param()c.req.query()c.req.valid()typescript
// ❌ 错误方式
const id = c.req.param('id') // string类型,无验证
// ✅ 正确方式
app.get('/users/:id', zValidator('param', idSchema), (c) => {
const { id } = c.req.valid('param') // 已验证的UUID
})Issue #8: Incorrect Middleware Order
问题8:中间件顺序错误
Error: Middleware executing in wrong order
Source: Official docs
Why It Happens: Misunderstanding middleware chain execution
Prevention: Remember middleware runs top-to-bottom, runs handler, then bottom-to-top
await next()typescript
app.use('*', async (c, next) => {
console.log('1: Before handler')
await next()
console.log('4: After handler')
})
app.use('*', async (c, next) => {
console.log('2: Before handler')
await next()
console.log('3: After handler')
})
app.get('/', (c) => {
console.log('Handler')
return c.json({})
})
// Output: 1, 2, Handler, 3, 4错误表现:中间件执行顺序不符合预期
来源:官方文档
原因:对中间件链执行逻辑理解有误
预防方案:记住中间件从上到下执行,执行处理函数,然后从下到上执行后续逻辑
await next()typescript
app.use('*', async (c, next) => {
console.log('1: Before handler')
await next()
console.log('4: After handler')
})
app.use('*', async (c, next) => {
console.log('2: Before handler')
await next()
console.log('3: After handler')
})
app.get('/', (c) => {
console.log('Handler')
return c.json({})
})
// 输出顺序: 1, 2, Handler, 3, 4Issue #9: JWT verify() Requires Algorithm Parameter (v4.11.4+)
问题9:JWT verify()需要算法参数(v4.11.4+)
Error:
Source: GitHub Issue #4625 | Security Advisory GHSA-f67f-6cw9-8mq4
Why It Happens: Security fix in v4.11.4 requires explicit algorithm specification to prevent JWT header manipulation
Prevention: Always specify the algorithm parameter
TypeError: Cannot read properties of undefinedtypescript
import { verify } from 'hono/jwt'
// ❌ Wrong (pre-v4.11.4 syntax)
const payload = await verify(token, secret)
// ✅ Correct (v4.11.4+)
const payload = await verify(token, secret, 'HS256') // Algorithm requiredNote: This was a breaking change released in a patch version due to security severity. Update all JWT verification code when upgrading to v4.11.4+.
错误表现:
来源:GitHub Issue #4625 | 安全公告GHSA-f67f-6cw9-8mq4
原因:v4.11.4的安全修复要求显式指定算法,以防止JWT头篡改
预防方案:始终指定算法参数
TypeError: Cannot read properties of undefinedtypescript
import { verify } from 'hono/jwt'
// ❌ 错误方式(v4.11.4之前的语法)
const payload = await verify(token, secret)
// ✅ 正确方式(v4.11.4+)
const payload = await verify(token, secret, 'HS256') // 必须指定算法注意:这是一个因安全严重性而在补丁版本中发布的破坏性变更。升级到v4.11.4+时,请更新所有JWT验证代码。
Issue #10: Request Body Consumed by Middleware
问题10:中间件消耗请求体
Error:
Source: GitHub Issue #4259
Why It Happens: Using bypasses Hono's cache and consumes the body stream
Prevention: Always use or instead of accessing raw request
TypeError: Body is unusablec.req.raw.clone()c.req.text()c.req.json()typescript
// ❌ Wrong - Breaks downstream validators
app.use('*', async (c, next) => {
const body = await c.req.raw.clone().text() // Consumes body!
console.log('Request body:', body)
await next()
})
app.post('/', zValidator('json', schema), async (c) => {
const data = c.req.valid('json') // Error: Body is unusable
return c.json({ data })
})
// ✅ Correct - Uses cached content
app.use('*', async (c, next) => {
const body = await c.req.text() // Cache-friendly
console.log('Request body:', body)
await next()
})
app.post('/', zValidator('json', schema), async (c) => {
const data = c.req.valid('json') // Works!
return c.json({ data })
})Why: Request bodies in Web APIs can only be read once (they're streams). Hono's validator internally uses which caches the content. If you use , it bypasses the cache and consumes the body, causing subsequent reads to fail.
await c.req.json()c.req.raw.clone().json()错误表现:
来源:GitHub Issue #4259
原因:使用会绕过Hono的缓存并消耗请求体流
预防方案:始终使用或而非直接访问原始请求
TypeError: Body is unusablec.req.raw.clone()c.req.text()c.req.json()typescript
// ❌ 错误方式 - 会破坏下游验证器
app.use('*', async (c, next) => {
const body = await c.req.raw.clone().text() // 消耗了请求体!
console.log('Request body:', body)
await next()
})
app.post('/', zValidator('json', schema), async (c) => {
const data = c.req.valid('json') // 错误: Body is unusable
return c.json({ data })
})
// ✅ 正确方式 - 使用缓存内容
app.use('*', async (c, next) => {
const body = await c.req.text() // 友好支持缓存
console.log('Request body:', body)
await next()
})
app.post('/', zValidator('json', schema), async (c) => {
const data = c.req.valid('json') // 正常工作!
return c.json({ data })
})原因:Web API中的请求体只能被读取一次(它们是流)。Hono的验证器内部使用,该方法会缓存内容。如果使用,会绕过缓存并消耗请求体,导致后续读取失败。
await c.req.json()c.req.raw.clone().json()Configuration Files Reference
配置文件参考
package.json (Full Example)
package.json(完整示例)
json
{
"name": "hono-app",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
},
"dependencies": {
"hono": "^4.11.4"
},
"devDependencies": {
"typescript": "^5.9.0",
"tsx": "^4.19.0",
"@types/node": "^22.10.0"
}
}json
{
"name": "hono-app",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
},
"dependencies": {
"hono": "^4.11.4"
},
"devDependencies": {
"typescript": "^5.9.0",
"tsx": "^4.19.0",
"@types/node": "^22.10.0"
}
}package.json with Validation (Zod)
带Zod验证的package.json
json
{
"dependencies": {
"hono": "^4.11.4",
"zod": "^4.3.5",
"@hono/zod-validator": "^0.7.6"
}
}json
{
"dependencies": {
"hono": "^4.11.4",
"zod": "^4.3.5",
"@hono/zod-validator": "^0.7.6"
}
}package.json with Validation (Valibot)
带Valibot验证的package.json
json
{
"dependencies": {
"hono": "^4.11.4",
"valibot": "^1.2.0",
"@hono/valibot-validator": "^0.6.1"
}
}json
{
"dependencies": {
"hono": "^4.11.4",
"valibot": "^1.2.0",
"@hono/valibot-validator": "^0.6.1"
}
}package.json with All Validators
带所有验证器的package.json
json
{
"dependencies": {
"hono": "^4.11.4",
"zod": "^4.3.5",
"valibot": "^1.2.0",
"@hono/zod-validator": "^0.7.6",
"@hono/valibot-validator": "^0.6.1",
"@hono/typia-validator": "^0.1.2",
"@hono/arktype-validator": "^2.0.1"
}
}json
{
"dependencies": {
"hono": "^4.11.4",
"zod": "^4.3.5",
"valibot": "^1.2.0",
"@hono/zod-validator": "^0.7.6",
"@hono/valibot-validator": "^0.6.1",
"@hono/typia-validator": "^0.1.2",
"@hono/arktype-validator": "^2.0.1"
}
}tsconfig.json
tsconfig.json
json
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"lib": ["ES2022"],
"moduleResolution": "bundler",
"resolveJsonModule": true,
"allowJs": true,
"checkJs": false,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"outDir": "./dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}json
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"lib": ["ES2022"],
"moduleResolution": "bundler",
"resolveJsonModule": true,
"allowJs": true,
"checkJs": false,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"outDir": "./dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}File Templates
文件模板
All templates are available in the directory:
templates/- routing-patterns.ts - Route params, query params, wildcards, grouping
- middleware-composition.ts - Middleware chaining, built-in middleware
- validation-zod.ts - Zod validation with custom hooks
- validation-valibot.ts - Valibot validation
- rpc-pattern.ts - Type-safe RPC client/server
- error-handling.ts - HTTPException, onError, custom errors
- context-extension.ts - c.set/c.get, custom context types
- package.json - All dependencies
Copy these files to your project and customize as needed.
所有模板可在目录中找到:
templates/- routing-patterns.ts - 路由参数、查询参数、通配符、分组
- middleware-composition.ts - 中间件链、内置中间件
- validation-zod.ts - 带自定义钩子的Zod验证
- validation-valibot.ts - Valibot验证
- rpc-pattern.ts - 类型安全RPC客户端/服务端
- error-handling.ts - HTTPException、onError、自定义错误
- context-extension.ts - c.set/c.get、自定义上下文类型
- package.json - 所有依赖项
复制这些文件到你的项目中并按需自定义。
Reference Documentation
参考文档
For deeper understanding, see:
- middleware-catalog.md - Complete built-in Hono middleware reference
- validation-libraries.md - Zod vs Valibot vs Typia vs ArkType comparison
- rpc-guide.md - RPC pattern deep dive, performance optimization
- top-errors.md - Common Hono errors with solutions
如需深入了解,请查看:
- middleware-catalog.md - 完整的Hono内置中间件参考
- validation-libraries.md - Zod vs Valibot vs Typia vs ArkType对比
- rpc-guide.md - RPC模式深入指南、性能优化
- top-errors.md - 常见Hono错误及解决方案
Official Documentation
官方文档
- Hono: https://hono.dev
- Hono Routing: https://hono.dev/docs/api/routing
- Hono Middleware: https://hono.dev/docs/guides/middleware
- Hono Validation: https://hono.dev/docs/guides/validation
- Hono RPC: https://hono.dev/docs/guides/rpc
- Hono Context: https://hono.dev/docs/api/context
- Context7 Library ID:
/llmstxt/hono_dev_llms-full_txt
- Hono: https://hono.dev
- Hono 路由: https://hono.dev/docs/api/routing
- Hono 中间件: https://hono.dev/docs/guides/middleware
- Hono 验证: https://hono.dev/docs/guides/validation
- Hono RPC: https://hono.dev/docs/guides/rpc
- Hono 上下文: https://hono.dev/docs/api/context
- Context7 库ID:
/llmstxt/hono_dev_llms-full_txt
Dependencies (Latest Verified 2026-01-20)
依赖项(2026-01-20 最新验证版本)
json
{
"dependencies": {
"hono": "^4.11.4"
},
"optionalDependencies": {
"zod": "^4.3.5",
"valibot": "^1.2.0",
"@hono/zod-validator": "^0.7.6",
"@hono/valibot-validator": "^0.6.1",
"@hono/typia-validator": "^0.1.2",
"@hono/arktype-validator": "^2.0.1"
},
"devDependencies": {
"typescript": "^5.9.0"
}
}json
{
"dependencies": {
"hono": "^4.11.4"
},
"optionalDependencies": {
"zod": "^4.3.5",
"valibot": "^1.2.0",
"@hono/zod-validator": "^0.7.6",
"@hono/valibot-validator": "^0.6.1",
"@hono/typia-validator": "^0.1.2",
"@hono/arktype-validator": "^2.0.1"
},
"devDependencies": {
"typescript": "^5.9.0"
}
}Production Example
生产环境示例
This skill is validated across multiple runtime environments:
- Cloudflare Workers: Routing, middleware, RPC patterns
- Deno: All validation libraries tested
- Bun: Performance benchmarks completed
- Node.js: Full test suite passing
All patterns in this skill have been validated in production.
Questions? Issues?
- Check first
references/top-errors.md - Verify all steps in the setup process
- Ensure is called in middleware
await next() - Ensure RPC routes use pattern
const route = app.get(...) - Check official docs: https://hono.dev
本技能已在多种运行时环境中验证:
- Cloudflare Workers: 路由、中间件、RPC模式
- Deno: 所有验证库测试通过
- Bun: 性能基准测试完成
- Node.js: 完整测试套件通过
本技能中的所有模式均已在生产环境中验证。
有疑问?遇到问题?
- 首先查看
references/top-errors.md - 验证设置过程中的所有步骤
- 确保中间件中调用了
await next() - 确保RPC路由使用模式
const route = app.get(...) - 查看官方文档: https://hono.dev