hono-routing by jezweb/claude-skills
npx skills add https://github.com/jezweb/claude-skills --skill hono-routing状态 : 生产就绪 ✅ 最后更新 : 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
npm install hono@4.11.4
选择 Hono 的理由:
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => {
return c.json({ message: 'Hello Hono!' })
})
export default app
关键点:
c.json()、、 来返回响应广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
c.text()c.html()res.send())npm install zod@4.3.5 @hono/zod-validator@0.7.6
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 })
})
为何需要验证:
// 单个参数
app.get('/users/:id', (c) => {
const id = c.req.param('id')
return c.json({ userId: id })
})
// 多个参数
app.get('/posts/:postId/comments/:commentId', (c) => {
const { postId, commentId } = c.req.param()
return c.json({ postId, commentId })
})
// 可选参数(使用通配符)
app.get('/files/*', (c) => {
const path = c.req.param('*')
return c.json({ filePath: path })
})
关键点:
c.req.param('name') 返回单个参数c.req.param() 返回所有参数作为一个对象在路由中使用正则表达式模式,在路由级别限制参数匹配:
// 仅匹配数字 ID
app.get('/users/:id{[0-9]+}', (c) => {
const id = c.req.param('id') // 保证是数字
return c.json({ userId: id })
})
// 仅匹配 UUID
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') // 保证是 UUID 格式
return c.json({ postId: id })
})
优点:
app.get('/search', (c) => {
// 单个查询参数
const q = c.req.query('q')
// 多个查询参数
const { page, limit } = c.req.query()
// 查询参数数组(例如,?tag=js&tag=ts)
const tags = c.req.queries('tag')
return c.json({ q, page, limit, tags })
})
最佳实践:
// 创建子应用
const api = new Hono()
api.get('/users', (c) => c.json({ users: [] }))
api.get('/posts', (c) => c.json({ posts: [] }))
// 挂载子应用
const app = new Hono()
app.route('/api', api)
// 结果:/api/users, /api/posts
为何要分组路由:
关键的中间件规则:
始终在中间件中调用 await next() 以继续链式执行
提前返回(不调用 next())以阻止处理程序执行
在 next() 之后 检查 c.error 以进行错误处理
app.use('/admin/*', async (c, next) => { const token = c.req.header('Authorization') if (!token) return c.json({ error: 'Unauthorized' }, 401) await next() // 必须调用! })
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()
// 请求日志记录
app.use('*', logger())
// CORS
app.use('/api/*', cors({
origin: 'https://example.com',
allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
allowHeaders: ['Content-Type', 'Authorization'],
}))
// 美化 JSON(仅开发环境)
app.use('*', prettyJSON())
// 压缩(gzip/deflate)
app.use('*', compress())
// 缓存响应
app.use(
'/static/*',
cache({
cacheName: 'my-app',
cacheControl: 'max-age=3600',
})
)
自定义缓存中间件模式:
当为 Node.js(或其他非 Cloudflare 运行时)实现自定义缓存中间件时,必须在将响应存储到缓存之前克隆它们:
const cache = new Map<string, Response>()
const customCache = async (c, next) => {
const key = c.req.url
// 检查缓存
const cached = cache.get(key)
if (cached) {
return cached.clone() // 从缓存返回时克隆
}
// 执行处理程序
await next()
// 存储到缓存(必须克隆!)
cache.set(key, c.res.clone()) // ✅ 存储前克隆
}
app.use('*', customCache)
为何需要克隆: 响应体是可读流,只能被消费一次。克隆会创建一个带有新流的新响应。
**内置中间件参考**: 参见 `references/middleware-catalog.md`
#### 流式助手(SSE、AI 响应)
```typescript
import { Hono } from 'hono'
import { stream, streamText, streamSSE } from 'hono/streaming'
const app = new Hono()
// 二进制流
app.get('/download', (c) => {
return stream(c, async (stream) => {
await stream.write(new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]))
await stream.pipe(readableStream)
})
})
// 文本流(AI 响应)
app.get('/ai', (c) => {
return streamText(c, async (stream) => {
for await (const chunk of aiResponse) {
await stream.write(chunk)
await stream.sleep(50) // 如果需要,进行速率限制
}
})
})
// 服务器发送事件(实时更新)
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() - 实时通知、实时数据流import { Hono } from 'hono'
import { upgradeWebSocket } from 'hono/cloudflare-workers' // 平台特定!
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),
// Cloudflare Workers 不支持 onOpen!
})))
export default app
⚠️ Cloudflare Workers WebSocket 注意事项:
从 hono/cloudflare-workers 导入(而不是 hono/ws)
不支持 onOpen 回调(Cloudflare 限制)
CORS/修改头部的中间件与 WebSocket 路由冲突
使用路由分组将 WebSocket 路由排除在 CORS 之外:
const api = new Hono() api.use('*', cors()) // 仅对 API 使用 CORS app.route('/api', api) app.get('/ws', upgradeWebSocket(...)) // WebSocket 不使用 CORS
import { Hono } from 'hono'
import { secureHeaders } from 'hono/secure-headers'
import { csrf } from 'hono/csrf'
const app = new Hono()
// 安全头部(X-Frame-Options、CSP、HSTS 等)
app.use('*', secureHeaders({
xFrameOptions: 'DENY',
xXssProtection: '1; mode=block',
contentSecurityPolicy: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
},
}))
// CSRF 保护(验证 Origin 头部)
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 白名单/黑名单 |
使用条件逻辑组合中间件:
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: 任意一个中间件必须通过(OR 逻辑)
app.use('/admin/*', some(
bearerAuth({ token: 'admin-token' }),
ipRestriction({ allowList: ['10.0.0.0/8'] }),
))
// every: 所有中间件都必须通过(AND 逻辑)
app.use('/secure/*', every(
bearerAuth({ token: 'secret' }),
ipRestriction({ allowList: ['192.168.1.0/24'] }),
))
// except: 为某些路径跳过中间件
app.use('*', except(
['/health', '/metrics'],
logger(),
))
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 }>()
// 中间件设置变量
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()
})
// 路由访问变量
app.get('/api/profile', (c) => {
const user = c.get('user') // 类型安全!
const requestId = c.get('requestId') // 类型安全!
return c.json({ user, requestId })
})
关键点:
c.get() 定义 Variables 类型Bindings 类型(Cloudflare Workers)c.set(),在处理程序中使用 c.get()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>()
// 创建日志记录器中间件
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('来自路由的问候')
return c.json({ message: 'Hello' })
})
高级模式 : 参见 templates/context-extension.ts
npm install zod@4.3.5 @hono/zod-validator@0.7.6
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
// 定义模式
const userSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(18).optional(),
})
// 验证 JSON 请求体
app.post('/users', zValidator('json', userSchema), (c) => {
const data = c.req.valid('json') // 类型安全!
return c.json({ success: true, data })
})
// 验证查询参数
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 })
})
// 验证路由参数
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 })
})
// 验证头部
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、cookiez.transform() 将字符串转换为数字/日期⚠️ 关键点:验证必须是处理程序特定的
为了使验证类型能正确推断,验证中间件必须在处理程序中添加,而不是通过 app.use():
// ❌ 错误 - 类型推断失效
app.use('/users', zValidator('json', userSchema))
app.post('/users', (c) => {
const data = c.req.valid('json') // TS 错误:类型 'never'
return c.json({ data })
})
// ✅ 正确 - 在处理程序中进行验证
app.post('/users', zValidator('json', userSchema), (c) => {
const data = c.req.valid('json') // 类型安全!
return c.json({ data })
})
原因: Hono 的 Input 类型映射使用泛型合并验证结果。当验证器通过 app.use() 应用时,类型系统无法跟踪哪些路由具有哪些验证模式,导致 Input 泛型坍缩为 never。
import { zValidator } from '@hono/zod-validator'
import { HTTPException } from 'hono/http-exception'
const schema = z.object({
name: z.string(),
age: z.number(),
})
// 自定义错误处理器
app.post(
'/users',
zValidator('json', schema, (result, c) => {
if (!result.success) {
// 自定义错误响应
return c.json(
{
error: '验证失败',
issues: result.error.issues,
},
400
)
}
}),
(c) => {
const data = c.req.valid('json')
return c.json({ success: true, data })
}
)
// 抛出 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 中修复。请确保使用最新版本:
npm install @hono/zod-validator@0.7.6
npm install valibot@1.2.0 @hono/valibot-validator@0.6.1
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
npm install typia @hono/typia-validator@0.1.2
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 的理由:
npm install arktype @hono/arktype-validator@2.0.1
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 获取详细对比
Hono 的 RPC 功能允许类型安全的客户端/服务器通信,而无需手动定义 API 类型。客户端直接从服务器路由推断类型。
// 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(),
})
// 定义路由并导出类型
const route = app.post(
'/users',
zValidator('json', schema),
(c) => {
const data = c.req.valid('json')
return c.json({ success: true, data }, 201)
}
)
// 为 RPC 客户端导出应用类型
export type AppType = typeof route
// 或者导出整个应用
// export type AppType = typeof app
export default app
关键点:
const route = app.get(...) 进行 RPC 类型推断typeof route 或 typeof app// client.ts
import { hc } from 'hono/client'
import type { AppType } from './app'
const client = hc<AppType>('http://localhost:8787')
// 类型安全的 API 调用
const res = await client.users.$post({
json: {
name: 'Alice',
age: 30,
},
})
// 响应是类型化的!
const data = await res.json() // { success: boolean, data: { name: string, age: number } }
为何使用 RPC:
⚠️ RPC 类型推断限制: RPC 客户端仅推断 json 和 text 响应的类型。如果一个端点返回多种响应类型(例如,JSON 和二进制),则所有响应都不会被类型推断:
// ❌ 类型推断失败 - 混合了 JSON 和二进制
app.post('/upload', async (c) => {
const body = await c.req.body() // 二进制响应
if (error) {
return c.json({ error: 'Bad request' }, 400) // JSON 响应
}
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) // 仅二进制 - 独立的端点
})
// 服务器
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 type AppType = typeof getUsers | typeof createUser | typeof getUser
// 客户端
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' },
})
问题 : 具有许多路由的大型应用导致类型推断缓慢
解决方案 : 导出特定的路由组,而不是整个应用
// ❌ 慢:导出整个应用
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
// 客户端导入特定路由
import type { UserRoutes } from './app'
const userClient = hc<UserRoutes>('http://localhost:8787')
深入探讨 : 参见 references/rpc-guide.md
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')
// 为客户端错误抛出 HTTPException
if (!id) {
throw new HTTPException(400, { message: 'ID 是必需的' })
}
// 使用自定义响应
if (id === 'invalid') {
const res = new Response('自定义错误体', { status: 400 })
throw new HTTPException(400, { res })
}
return c.json({ id })
})
关键点:
onError 代替import { Hono } from 'hono'
import { HTTPException } from 'hono/http-exception'
const app = new Hono()
// 自定义错误处理器
app.onError((err, c) => {
// 处理 HTTPException
if (err instanceof HTTPException) {
return err.getResponse()
}
// 处理意外错误
console.error('意外错误:', err)
return c.json(
{
error: '内部服务器错误',
message: err.message,
},
500
)
})
app.get('/error', (c) => {
throw new Error('出错了!')
})
为何使用 onError:
app.use('*', async (c, next) => {
await next()
// 在处理程序后检查错误
if (c.error) {
console.error('路由中的错误:', c.error)
// 发送到错误跟踪服务
}
})
app.notFound((c) => {
return c.json({ error: '未找到' }, 404)
})
✅ 在中间件中调用 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 类型推断所必需
❌ 在中间件中忘记 await next() - 破坏中间件链 ❌ 像 Express 那样使用 res.send() - 与 Hono 不兼容 ❌ 未经验证访问请求数据 - 为类型安全使用验证器 ❌ 为大型 RPC 导出整个应用 - 类型推断缓慢,应导出特定路由 ❌ 使用普通的 throw new Error() - 应使用 HTTPException 代替 ❌ 跳过 onError 处理器 - 导致不一致的错误响应 ❌ 不使用 Variables 类型就使用 c.set/c.get - 失去类型安全
此技能可预防 10 个已记录的问题:
错误 : 具有许多路由时 IDE 变慢(8 分钟 CI 构建,IntelliSense 不存在) 来源 : hono/docs/guides/rpc | GitHub Issue #3869 原因 : 来自 typeof app 的复杂类型实例化,涉及许多路由。Zod 方法如 omit、extend、pick 会加剧此问题。 预防 : 导出特定的路由组,而不是整个应用
// ❌ 慢
export type AppType = typeof app
// ✅ 快
const userRoutes = app.get(...).post(...)
export type UserRoutes = typeof userRoutes
大型应用的高级解决方案(100+ 路由):
// 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
2. 使用单独的构建配置 :
* **生产环境** : 完整的 `tsc` 并生成 `.d.ts` 文件(供 RPC 客户端使用)
* **开发环境** : 在主路由器上跳过 `tsc`,仅对子路由器进行类型检查(更快的实时重载)
3. 避免影响性能的 Zod 方法 :
* `z.omit()`、`z.extend()`、`z.pick()` \- 这些方法会使语言服务器的工作量增加 10 倍
* 可能时使用接口代替交叉类型
错误 : 中间件响应(包括 notFound() 和 onError())未被 RPC 客户端推断 来源 : honojs/hono#2719 | GitHub Issue #4600 原因 : RPC 模式默认不推断中间件响应。来自 notFound() 或 onError() 处理程序的响应不包含在类型映射中。 预防 : 导出包含中间件的特定路由类型
const route = app.get(
'/data',
myMiddleware,
(c) => c.json({ data: 'value' })
)
export type AppType = typeof route
特定问题:notFound/onError 未类型化:
// 服务器
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 })
})
// 客户端
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 类型:
import { Hono, TypedResponse } from 'hono'
declare module 'hono' {
interface NotFoundResponse
extends Response,
TypedResponse<{ error: string }, 404, 'json'> {}
}
错误 : 不同的验证器库有不同的钩子模式 来源 : Context7 研究 原因 : 每个验证器(@hono/zod-validator、@hono/valibot-validator 等)都有略微不同的 API 预防 : 此技能为所有验证器提供一致的模式
错误 : 抛出普通的 Error 而不是 HTTPException 来源 : 官方文档 原因 : 熟悉 Express 的开发人员使用 throw new Error() 预防 : 始终为客户端错误(400-499)使用 HTTPException
// ❌ 错误
throw new Error('Unauthorized')
// ✅ 正确
throw new HTTPException(401, { message: 'Unauthorized' })
错误 : 没有类型推断的 c.set() 和
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
npm install hono@4.11.4
Why Hono:
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => {
return c.json({ message: 'Hello Hono!' })
})
export default app
CRITICAL:
c.json(), c.text(), c.html() for responsesres.send() like Express)npm install zod@4.3.5 @hono/zod-validator@0.7.6
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:
// 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 parameterc.req.param() returns all parameters as objectUse regex patterns in routes to restrict parameter matching at the routing level:
// 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:
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:
// 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:
CRITICAL Middleware Rule:
Always callawait next() in middleware to continue the chain
Return early (without calling next()) to prevent handler execution
Check c.error AFTER next() for error handling
app.use('/admin/*', async (c, next) => { const token = c.req.header('Authorization') if (!token) return c.json({ error: 'Unauthorized' }, 401) await next() // Required! })
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:
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`
#### Streaming Helpers (SSE, AI Responses)
```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, audiostreamText() - AI chat responses, typewriter effectsstreamSSE() - Real-time notifications, live feedsimport { 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:
const api = new Hono() api.use('*', cors()) // CORS for API only app.route('/api', api) app.get('/ws', upgradeWebSocket(...)) // No CORS on WebSocket
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 |
|---|---|
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 |
Compose middleware with conditional logic:
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(),
))
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:
Variables type for type-safe c.get()Bindings type for environment variables (Cloudflare Workers)c.set() in middleware, c.get() in handlersimport { 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
npm install zod@4.3.5 @hono/zod-validator@0.7.6
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:
c.req.valid() after validation (type-safe)json, query, param, header, form, cookiez.transform() to convert strings to numbers/dates⚠️ 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():
// ❌ 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.
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:
npm install @hono/zod-validator@0.7.6
npm install valibot@1.2.0 @hono/valibot-validator@0.6.1
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
npm install typia @hono/typia-validator@0.1.2
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:
npm install arktype @hono/arktype-validator@2.0.1
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
Hono's RPC feature allows type-safe client/server communication without manual API type definitions. The client infers types directly from the server routes.
// 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:
const route = app.get(...) for RPC type inferencetypeof route or typeof app// 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:
⚠️ 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:
// ❌ 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
})
// 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' },
})
Problem : Large apps with many routes cause slow type inference
Solution : Export specific route groups instead of entire app
// ❌ 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
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:
onError insteadimport { 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:
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
}
})
app.notFound((c) => {
return c.json({ error: 'Not Found' }, 404)
})
✅ Callawait next() in middleware - Required for middleware chain execution ✅ Return Response from handlers - Use c.json(), c.text(), c.html() ✅ Usec.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 ✅ UseonError 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
❌ Forgetawait next() in middleware - Breaks middleware chain ❌ Useres.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
This skill prevents 10 documented issues:
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
// ❌ Slow
export type AppType = typeof app
// ✅ Fast
const userRoutes = app.get(...).post(...)
export type UserRoutes = typeof userRoutes
Advanced Workaround for Large Apps (100+ routes):
// 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
2. 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)
3. 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
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
const route = app.get(
'/data',
myMiddleware,
(c) => c.json({ data: 'value' })
)
export type AppType = typeof route
Specific Issue: notFound/onError Not Typed:
// 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:
import { Hono, TypedResponse } from 'hono'
declare module 'hono' {
interface NotFoundResponse
extends Response,
TypedResponse<{ error: string }, 404, 'json'> {}
}
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
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)
// ❌ Wrong
throw new Error('Unauthorized')
// ✅ Correct
throw new HTTPException(401, { message: 'Unauthorized' })
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
type Variables = {
user: { id: number; name: string }
}
const app = new Hono<{ Variables: Variables }>()
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
app.use('*', async (c, next) => {
await next()
if (c.error) {
console.error('Error:', c.error)
}
})
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()
// ❌ 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
})
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
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
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
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+.
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
// ❌ 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.
{
"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"
}
}
{
"dependencies": {
"hono": "^4.11.4",
"zod": "^4.3.5",
"@hono/zod-validator": "^0.7.6"
}
}
{
"dependencies": {
"hono": "^4.11.4",
"valibot": "^1.2.0",
"@hono/valibot-validator": "^0.6.1"
}
}
{
"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"
}
}
{
"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"]
}
All templates are available in the templates/ directory:
Copy these files to your project and customize as needed.
For deeper understanding, see:
/llmstxt/hono_dev_llms-full_txt{
"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"
}
}
This skill is validated across multiple runtime environments:
All patterns in this skill have been validated in production.
Questions? Issues?
references/top-errors.md firstawait next() is called in middlewareconst route = app.get(...) patternWeekly Installs
615
Repository
GitHub Stars
652
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubFailSocketPassSnykPass
Installed on
claude-code451
opencode421
gemini-cli396
codex375
cursor320
github-copilot308
飞书OpenAPI Explorer:探索和调用未封装的飞书原生API接口
15,500 周安装