hono-routing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Hono 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.4
Why 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 app
CRITICAL:
  • Use
    c.json()
    ,
    c.text()
    ,
    c.html()
    for responses
  • Return the response (don't use
    res.send()
    like Express)
  • 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.6
typescript
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.6
typescript
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:
  • c.req.param('name')
    returns single parameter
  • c.req.param()
    returns all parameters as object
  • 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/posts
Why 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
    await next()
    in middleware to continue the chain
  • Return early (without calling
    next()
    ) to prevent handler execution
  • Check
    c.error
    AFTER
    next()
    for error handling
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.md

Streaming 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:
  • stream()
    - Binary files, video, audio
  • streamText()
    - AI chat responses, typewriter effects
  • streamSSE()
    - Real-time notifications, live feeds
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()
    - 二进制文件、视频、音频
  • streamText()
    - AI聊天响应、打字机效果
  • 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
    hono/cloudflare-workers
    (not
    hono/ws
    )
  • onOpen
    callback is NOT supported (Cloudflare limitation)
  • 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 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 注意事项:
  • hono/cloudflare-workers
    导入(而非
    hono/ws
  • onOpen
    回调不被支持(Cloudflare限制)
  • 修改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 WebSocket

Security 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:
MiddlewarePurpose
secureHeaders
X-Frame-Options, CSP, HSTS, XSS protection
csrf
CSRF via Origin/Sec-Fetch-Site validation
bearerAuth
Bearer token authentication
basicAuth
HTTP Basic authentication
ipRestriction
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'],
}))
安全中间件选项:
中间件用途
secureHeaders
提供X-Frame-Options、CSP、HSTS、XSS防护等安全标头
csrf
通过Origin/Sec-Fetch-Site验证防范CSRF攻击
bearerAuth
Bearer令牌身份验证
basicAuth
HTTP基础身份验证
ipRestriction
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
    Variables
    type for type-safe
    c.get()
  • Define
    Bindings
    type for environment variables (Cloudflare Workers)
  • c.set()
    in middleware,
    c.get()
    in handlers
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 })
})
重要提示:
  • 定义
    Variables
    类型以实现类型安全的
    c.get()
  • 定义
    Bindings
    类型以适配环境变量(Cloudflare Workers)
  • 在中间件中使用
    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.ts

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' })
})
高级模式:详见
templates/context-extension.ts

Part 4: Request Validation

第四部分:请求验证

Validation with Zod

使用Zod进行验证

bash
npm install zod@4.3.5 @hono/zod-validator@0.7.6
typescript
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
    c.req.valid()
    after validation (type-safe)
  • Validation targets:
    json
    ,
    query
    ,
    param
    ,
    header
    ,
    form
    ,
    cookie
  • Use
    z.transform()
    to convert strings to numbers/dates
  • 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
Input
type mapping merges validation results using generics. When validators are applied via
app.use()
, the type system cannot track which routes have which validation schemas, causing the
Input
generic to collapse to
never
.
bash
npm install zod@4.3.5 @hono/zod-validator@0.7.6
typescript
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
    form
    cookie
  • 使用
    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的
Input
类型映射通过泛型合并验证结果。当验证器通过
app.use()
全局应用时,类型系统无法跟踪哪些路由使用了哪些验证模式,导致
Input
泛型退化为
never

Custom 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
@hono/zod-validator@0.7.6
, 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:
bash
npm install @hono/zod-validator@0.7.6
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 })
  }
)
关于Zod可选枚举的注意事项:
@hono/zod-validator@0.7.6
版本之前,可选枚举会错误地解析为字符串类型而非枚举类型。该问题在v0.7.6中已修复,请确保使用最新版本:
bash
npm install @hono/zod-validator@0.7.6

Validation with Valibot

使用Valibot进行验证

bash
npm install valibot@1.2.0 @hono/valibot-validator@0.6.1
typescript
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.md
bash
npm install valibot@1.2.0 @hono/valibot-validator@0.6.1
typescript
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.md

Validation with Typia

使用Typia进行验证

bash
npm install typia @hono/typia-validator@0.1.2
typescript
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.2
typescript
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.1
typescript
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
references/validation-libraries.md
for detailed comparison

bash
npm install arktype @hono/arktype-validator@2.0.1
typescript
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.md

Part 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 app
CRITICAL:
  • Must use
    const route = app.get(...)
    for RPC type inference
  • Export
    typeof route
    or
    typeof 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
重要提示:
  • 必须使用
    const route = app.get(...)
    模式
    以支持RPC类型推断
  • 导出
    typeof route
    typeof 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
json
and
text
responses. If an endpoint returns multiple response types (e.g., JSON and binary), none of the responses will be type-inferred:
typescript
// ❌ 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
text
类型的响应。如果端点返回多种响应类型(如JSON和二进制),所有响应的类型都将无法被推断:
typescript
// ❌ 类型推断失效 - 混合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.md

Part 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
    onError
    instead
  • 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
await next()
in middleware
- Required for middleware chain execution ✅ Return Response from handlers - Use
c.json()
,
c.text()
,
c.html()
Use
c.req.valid()
after validation
- Type-safe validated data ✅ Export route types for RPC -
export type AppType = typeof route
Throw HTTPException for client errors - 400, 401, 403, 404 errors ✅ Use
onError
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()
- 获取类型安全的验证数据 ✅ 为RPC导出路由类型 -
export type AppType = typeof route
客户端错误抛出HTTPException - 400、401、403、404错误使用该方式 ✅ 使用
onError
进行全局错误处理
- 集中统一的错误响应 ✅ 为c.set/c.get定义Variables类型 - 类型安全的上下文变量 ✅ 使用const route = app.get(...)模式 - RPC类型推断的必要条件

Never Do

禁止操作

Forget
await next()
in middleware
- Breaks middleware chain ❌ Use
res.send()
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()
- 会中断中间件链 ❌ 像Express一样使用
res.send()
- 与Hono不兼容 ❌ 不验证就访问请求数据 - 使用验证器确保类型安全 ❌ 大型RPC应用导出整个应用 - 类型推断速度慢,应导出特定路由 ❌ 直接抛出new Error() - 改用HTTPException ❌ 跳过onError处理器 - 会导致错误响应格式不一致 ❌ 不定义Variables类型就使用c.set/c.get - 丢失类型安全

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
typeof app
with many routes. Exacerbated by Zod methods like
omit
,
extend
,
pick
. Prevention: Export specific route groups instead of entire app
typescript
// ❌ Slow
export type AppType = typeof app

// ✅ Fast
const userRoutes = app.get(...).post(...)
export type UserRoutes = typeof userRoutes
Advanced Workaround for Large Apps (100+ routes):
  1. 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
  1. Use separate build configs:
    • Production: Full
      tsc
      with
      .d.ts
      generation (for RPC client)
    • Development: Skip
      tsc
      on main router, only type-check sub-routers (faster live-reload)
  2. Avoid Zod methods that hurt performance:
    • z.omit()
      ,
      z.extend()
      ,
      z.pick()
      - These increase language server workload by 10x
    • Use interfaces instead of intersections when possible
错误表现:路由过多时IDE响应变慢(CI构建耗时8分钟,智能提示失效) 来源hono/docs/guides/rpc | GitHub Issue #3869 原因
typeof app
的复杂类型实例化导致。Zod的
omit
extend
pick
等方法会加剧该问题。 预防方案:导出特定路由组而非整个应用
typescript
// ❌ 速度慢
export type AppType = typeof app

// ✅ 速度快
const userRoutes = app.get(...).post(...)
export type UserRoutes = typeof userRoutes
大型应用高级解决方案(100+路由):
  1. 拆分为单体仓库模块
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
  1. 使用单独的构建配置
    • 生产环境:使用完整
      tsc
      生成
      .d.ts
      文件(供RPC客户端使用)
    • 开发环境:跳过主路由的
      tsc
      检查,仅对子路由进行类型检查(提升热重载速度)
  2. 避免使用影响性能的Zod方法
    • z.omit()
      z.extend()
      z.pick()
      - 会使语言服务器负载增加10倍
    • 尽可能使用接口而非交叉类型

Issue #2: Middleware Response Not Typed in RPC

问题2:中间件响应未在RPC中类型化

Error: Middleware responses (including
notFound()
and
onError()
) 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
notFound()
or
onError()
handlers are not included in type map. Prevention: Export specific route types that include middleware
typescript
const route = app.get(
  '/data',
  myMiddleware,
  (c) => c.json({ data: 'value' })
)
export type AppType = typeof route
Specific 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
NotFoundResponse
type:
typescript
import { Hono, TypedResponse } from 'hono'

declare module 'hono' {
  interface NotFoundResponse
    extends Response,
      TypedResponse<{ error: string }, 404, 'json'> {}
}
错误表现:中间件响应(包括
notFound()
onError()
)未被RPC客户端推断 来源honojs/hono#2719 | GitHub Issue #4600 原因:RPC模式默认不推断中间件响应。
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+): 使用模块扩展自定义
NotFoundResponse
类型:
typescript
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
throw new Error()
Prevention: Always use
HTTPException
for client errors (400-499)
typescript
// ❌ Wrong
throw new Error('Unauthorized')

// ✅ Correct
throw new HTTPException(401, { message: 'Unauthorized' })
错误表现:抛出普通Error而非HTTPException 来源:官方文档 原因:熟悉Express的开发者习惯使用
throw new Error()
预防方案:客户端错误(400-499)始终使用HTTPException
typescript
// ❌ 错误方式
throw new Error('Unauthorized')

// ✅ 正确方式
throw new HTTPException(401, { message: 'Unauthorized' })

Issue #5: Context Type Safety Lost

问题5:上下文类型安全丢失

Error:
c.set()
and
c.get()
without type inference Source: Official docs Why It Happens: Not defining
Variables
type in Hono generic Prevention: Always define Variables type
typescript
type Variables = {
  user: { id: number; name: string }
}

const app = new Hono<{ Variables: Variables }>()
错误表现
c.set()
c.get()
无类型推断 来源:官方文档 原因:未在Hono泛型中定义
Variables
类型 预防方案:始终定义Variables类型
typescript
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
c.error
after
await next()
Prevention: Check
c.error
in middleware
typescript
app.use('*', async (c, next) => {
  await next()
  if (c.error) {
    console.error('Error:', c.error)
  }
})
错误表现:处理函数中的错误未被捕获 来源:官方文档 原因:未在
await next()
后检查
c.error
预防方案:在中间件中检查
c.error
typescript
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
c.req.param()
or
c.req.query()
without validation Source: Best practices Why It Happens: Developers skip validation for speed Prevention: Always use validators and
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,
await next()
runs handler, then bottom-to-top
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, 4

Issue #9: JWT verify() Requires Algorithm Parameter (v4.11.4+)

问题9:JWT verify()需要算法参数(v4.11.4+)

Error:
TypeError: Cannot read properties of undefined
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
typescript
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 required
Note: 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+.
错误表现
TypeError: Cannot read properties of undefined
来源GitHub Issue #4625 | 安全公告GHSA-f67f-6cw9-8mq4 原因:v4.11.4的安全修复要求显式指定算法,以防止JWT头篡改 预防方案:始终指定算法参数
typescript
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:
TypeError: Body is unusable
Source: GitHub Issue #4259 Why It Happens: Using
c.req.raw.clone()
bypasses Hono's cache and consumes the body stream Prevention: Always use
c.req.text()
or
c.req.json()
instead of accessing raw request
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
await c.req.json()
which caches the content. If you use
c.req.raw.clone().json()
, it bypasses the cache and consumes the body, causing subsequent reads to fail.

错误表现
TypeError: Body is unusable
来源GitHub Issue #4259 原因:使用
c.req.raw.clone()
会绕过Hono的缓存并消耗请求体流 预防方案:始终使用
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
templates/
directory:
  • 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

官方文档



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?
  1. Check
    references/top-errors.md
    first
  2. Verify all steps in the setup process
  3. Ensure
    await next()
    is called in middleware
  4. Ensure RPC routes use
    const route = app.get(...)
    pattern
  5. Check official docs: https://hono.dev
本技能已在多种运行时环境中验证:
  • Cloudflare Workers: 路由、中间件、RPC模式
  • Deno: 所有验证库测试通过
  • Bun: 性能基准测试完成
  • Node.js: 完整测试套件通过
本技能中的所有模式均已在生产环境中验证。

有疑问?遇到问题?
  1. 首先查看
    references/top-errors.md
  2. 验证设置过程中的所有步骤
  3. 确保中间件中调用了
    await next()
  4. 确保RPC路由使用
    const route = app.get(...)
    模式
  5. 查看官方文档: https://hono.dev