关于 hono
hono 是面向 AI 的 Hono 开发技能,提供 API 参考,并指导使用 Hono CLI 检查和测试应用。
主要功能
- 01内嵌 Hono API 参考
- 02指导获取最新文档
- 03使用 CLI 检查路由
- 04无服务器请求与批量测试
- 05捕获并回归测试响应
适用场景
- 01创建或修改 Hono 路由
- 02在不启动服务器时测试端点
- 03保存现有 API 行为并验证改动
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ honojs/ skills --skill hono安装后在 Claude Code 中输入/hono确认已加载。
详细介绍
Hono 技能
构建 Hono Web 应用。此技能为 AI 提供内联 API 知识。使用 Hono CLI 检查和测试应用。
最新文档
如需了解此内联参考之外的详细信息,请从 https://hono.dev 获取最新文档。通过 https://hono.dev/llms.txt 获取文档页面索引,然后使用 Accept: text/markdown 标头获取 Markdown 格式的页面:
curl -H "Accept: text/markdown" https://hono.dev/docs/helpers/cookie
Hono CLI
请使用 Hono CLI 进行验证,不要使用临时脚本或开发服务器。它是开发依赖项(通过 create-hono 创建的项目已包含它),会在进程内使用 app.request() 加载应用,并输出 JSON。如果尚未安装,请安装,然后查看帮助信息:
npm install -D @hono/cli@next
npx hono --help
请遵循顶部的说明。其中解释了 JSON 输出约定和工作流。npx hono <command> --help 会提供每个命令(routes、request、batch、snapshot、benchmark、ssg、init)的示例和说明。
注意:
-
npx hono routes会列出路由;npx hono request /path会发送一个请求,无需启动服务。如果应用不在默认路径(src/index.ts),请为每个命令传入文件:npx hono request / src/app.ts。不要直接在 CLI 参数中传入凭据;敏感值请使用环境变量。 -
Cloudflare 绑定(KV、D1、R2、vars)会自动传入
c.env,无需服务或标志。在包含cloudflare.config.ts(cfCLI)和 Vite 配置的项目中,request、batch和snapshot默认通过 Vite 开发服务器运行,并且c.env包含本地绑定;在没有src/index.*的 Vite 项目中(由 Vite 插件构建的应用),request和batch也默认使用 Vite(snapshot仍需要文件),而--runtime vite可显式选择它。在包含 wrangler 配置的项目中,应用会在 Node.js 上运行,并加载真实的本地绑定;如需在此处使用完整的 Workers 运行时,请添加--runtime workerd(不要传入文件参数;入口是 wrangler 配置中的main)。--trace、--watch和文件参数会使应用在 Node.js 上运行,此时cf项目中的c.env为空。 -
routes和snapshot也能加载导入cloudflare:workers(Durable Objects)的应用;读取路由时会为该导入提供存根。 -
如需将 Hono 应用添加到现有目录,请运行
npx hono init --template cloudflare-workers(或nodejs、bun、deno……;如果不指定--template,则会根据目录中的文件进行检测),然后安装依赖项。它会复制create-hono模板,且绝不会覆盖现有文件。对于空目录,npm create hono@next也会通过提示完成相同操作。 -
对于多个请求,或需要保持状态的流程(POST,然后使用返回的 id),请在一次
hono batch -调用中运行。每行一个 JSON 对象;使用save保存值,并在后续步骤中将其用作{{id}}。这些步骤共用一个应用实例。使用expect声明每步预期的状态码和正文(正文会进行深度部分匹配),并持续迭代,直到摘要显示"failed": 0:npx hono batch - <<'EOF' {"method":"POST","path":"/users","body":{"name":"Alice"},"save":{"id":".id"},"expect":{"status":201}} {"path":"/users/{{id}}","expect":{"status":200,"body":{"name":"Alice"}}} EOF -
修改现有路由之前,请先捕获当前行为:
npx hono snapshot会将其输出为 batch JSONL 行(真实响应会成为expect)。保留这些行,完成修改后,再使用npx hono batch -重放,直到"failed": 0。 -
对于大型 API,请使用
npx hono snapshot --status-only和npx hono batch - --compact,流程相同,但输出要小得多。失败的步骤会附带diff:请修复其中指出的问题。
Hono API 参考
应用构造函数
import { Hono } from 'hono'
const app = new Hono()
// With TypeScript generics
type AppEnv = {
Bindings: Env // generated from the bindings config — see "Environment (Cloudflare Workers)"
Variables: { user: User }
}
const app = new Hono<AppEnv>()
路由方法
app.get('/path', handler)
app.post('/path', handler)
app.put('/path', handler)
app.delete('/path', handler)
app.patch('/path', handler)
app.options('/path', handler)
app.all('/path', handler) // all HTTP methods
app.on('PURGE', '/path', handler) // custom method
app.on(['PUT', 'DELETE'], '/path', handler) // multiple methods
路由模式
// Path parameters
app.get('/user/:name', (c) => {
const name = c.req.param('name')
return c.json({ name })
})
// Multiple params
app.get('/posts/:id/comments/:commentId', (c) => {
const { id, commentId } = c.req.param()
})
// Optional parameters
app.get('/api/animal/:type?', (c) => c.text('Animal!'))
// Wildcards
app.get('/wild/*/card', (c) => c.text('Wildcard'))
// Regexp constraints
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
const { date, title } = c.req.param()
})
// Chained routes
app
.get('/endpoint', (c) => c.text('GET'))
.post((c) => c.text('POST'))
.delete((c) => c.text('DELETE'))
路由分组
// Using route()
const api = new Hono()
api.get('/users', (c) => c.json([]))
const app = new Hono()
app.route('/api', api) // mounts at /api/users
// Using basePath()
const app = new Hono().basePath('/api')
app.get('/users', (c) => c.json([])) // GET /api/users
错误处理
app.notFound((c) => c.json({ message: 'Not Found' }, 404))
app.onError((err, c) => {
console.error(err)
return c.json({ message: 'Internal Server Error' }, 500)
})
上下文(c)
响应方法
c.text('Hello') // text/plain
c.json({ message: 'Hello' }) // application/json
c.html('<h1>Hello</h1>') // text/html
c.redirect('/new-path') // 302 redirect
c.redirect('/new-path', 301) // 301 redirect
c.body('raw body', 200, headers) // raw response
c.notFound() // 404 response
标头和状态码
c.status(201)
c.header('X-Custom', 'value')
c.header('Cache-Control', 'no-store')
变量(请求范围内的数据)
// In middleware
c.set('user', { id: 1, name: 'Alice' })
// In handler
const user = c.get('user')
// or
const user = c.var.user
环境(Cloudflare Workers)
切勿手动编写 Bindings 类型:应生成它,并在每次更改绑定后重新生成。项目使用的工具可以从文件中看出。
cf CLI 项目(cloudflare.config.ts;这是 create-hono 现在为 cloudflare-workers 生成的配置):使用 cf/config 中的 bindings 辅助函数,在 cloudflare.config.ts 中声明绑定。本地绑定无需 ID;除非用户要求,否则不要创建 Cloudflare 资源(KV、D1、R2)或设置 ID。然后运行 npm run typecheck(即 cf workers types && tsc;会写入 .cloudflare/types),并使用生成的 Env:
// cloudflare.config.ts
import { bindings, defineConfig } from 'cf/config'
import * as entrypoint from './src/index.ts' with { type: 'cf-worker' }
export default defineConfig({
worker: {
name: 'my-app',
compatibilityDate: '2026-10-06',
entrypoint,
env: {
MY_KV: bindings.kv(),
MY_VAR: bindings.text('my-variable'),
},
},
})
const app = new Hono<{ Bindings: Env }>()
脚本为 cf dev、cf build 和 cf deploy。这类项目没有 wrangler 配置;不要添加配置,也不要在 Hono 项目中运行 cf init(其自动配置会在 Hono 项目中停止)。
wrangler 项目(wrangler.jsonc 或 wrangler.toml):绑定位于 wrangler 配置中。npm run cf-typegen(即 wrangler types --env-interface CloudflareBindings)会写入 worker-configuration.d.ts,其中声明了 CloudflareBindings 以及 Workers 运行时类型。如果没有 cf-typegen 脚本,请运行 npx wrangler types,并使用其中声明的接口(Env,也可通过 Cloudflare.Env 获取)。@cloudflare/workers-types 已被两者取代:不要安装它。
const app = new Hono<{ Bindings: CloudflareBindings }>()
app.get('/', async (c) => {
const value = await c.env.MY_KV.get('key')
const db = c.env.MY_DB
c.executionCtx.waitUntil(promise)
})
渲染器
app.use(async (c, next) => {
c.setRenderer((content) =>
c.html(
<html><body>{content}</body></html>
)
)
await next()
})
app.get('/', (c) => c.render(<h1>Hello</h1>))
HonoRequest(c.req)
c.req.param('id') // path parameter
c.req.param() // all path params as object
c.req.query('page') // query string parameter
c.req.query() // all query params; values are string | undefined
c.req.queries('tags') // multiple values: ?tags=A&tags=B → ['A', 'B']
c.req.header('Authorization') // request header
c.req.header() // all headers (keys are lowercase)
// Body parsing
await c.req.json<{ title: string }>() // parse JSON body (unknown without the type argument)
await c.req.text() // parse text body
await c.req.formData() // parse as FormData
await c.req.parseBody() // parse multipart/form-data or urlencoded
await c.req.arrayBuffer() // parse as ArrayBuffer
await c.req.blob() // parse as Blob
// Validated data (used with validator middleware)
c.req.valid('json')
c.req.valid('query')
c.req.valid('form')
c.req.valid('param')
// Properties
c.req.url // full URL string
c.req.path // pathname
c.req.method // HTTP method
c.req.raw // underlying Request object
中间件
使用内置中间件
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { basicAuth } from 'hono/basic-auth'
import { prettyJSON } from 'hono/pretty-json'
import { secureHeaders } from 'hono/secure-headers'
import { etag } from 'hono/etag'
import { compress } from 'hono/compress'
import { poweredBy } from 'hono/powered-by'
import { timing } from 'hono/timing'
import { cache } from 'hono/cache'
import { bearerAuth } from 'hono/bearer-auth'
import { jwt } from 'hono/jwt'
import { jwk } from 'hono/jwk'
import { csrf } from 'hono/csrf'
import { ipRestriction } from 'hono/ip-restriction'
import { bodyLimit } from 'hono/body-limit'
import { timeout } from 'hono/timeout'
import { requestId } from 'hono/request-id'
import { methodOverride } from 'hono/method-override'
import { methodNotAllowed } from 'hono/method-not-allowed'
import { languageDetector } from 'hono/language'
import { some, every, except } from 'hono/combine'
import { contextStorage, getContext } from 'hono/context-storage'
import { trailingSlash, trimTrailingSlash } from 'hono/trailing-slash'
// Registration
app.use(logger()) // all routes
app.use('/api/*', cors()) // specific path
app.post('/api/*', basicAuth({ username: 'admin', password: 'secret' }))
自定义中间件
// Inline
app.use(async (c, next) => {
const start = Date.now()
await next()
const elapsed = Date.now() - start
c.res.headers.set('X-Response-Time', `${elapsed}ms`)
})
// Reusable with createMiddleware
import { createMiddleware } from 'hono/factory'
const auth = createMiddleware(async (c, next) => {
const token = c.req.header('Authorization')
if (!token) return c.json({ error: 'Unauthorized' }, 401)
await next()
})
app.use('/api/*', auth)
中间件执行顺序
中间件按注册顺序执行。await next() 会调用下一个中间件或处理程序,而 next() 之后的代码会在返回过程中运行:
Request → mw1 before → mw2 before → handler → mw2 after → mw1 after → Response
app.use(async (c, next) => {
// before handler
await next()
// after handler
})
验证
验证目标:json、form、query、header、param、cookie。
Zod 验证器
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const schema = z.object({
title: z.string().min(1),
body: z.string()
})
app.post('/posts', zValidator('json', schema), (c) => {
const data = c.req.valid('json') // fully typed
return c.json(data, 201)
})
Valibot / Standard Schema 验证器
import { sValidator } from '@hono/standard-validator'
import * as v from 'valibot'
const schema = v.object({ name: v.string(), age: v.number() })
app.post('/users', sValidator('json', schema), (c) => {
const data = c.req.valid('json')
return c.json(data, 201)
})
JSX
如需使用它构建 UI(借助 jsxRenderer 实现布局、使用 Vite、编写客户端代码),请使用 hono-jsx 技能。本节仅介绍语法。
设置
在 tsconfig.json 中:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
}
或者使用 pragma:/** @jsxImportSource hono/jsx */
**重要:**使用 JSX 的文件必须使用 .tsx 扩展名。请将 .ts 重命名为 .tsx,否则编译器会报错。
组件
import type { PropsWithChildren } from 'hono/jsx'
const Layout = (props: PropsWithChildren) => (
<html>
<head>
<title>My App</title>
</head>
<body>{props.children}</body>
</html>
)
const UserCard = ({ name }: { name: string }) => (
<div class="card">
<h2>{name}</h2>
</div>
)
app.get('/', (c) => {
return c.html(
<Layout>
<UserCard name="Alice" />
</Layout>
)
})
jsxRenderer 中间件
使用 jsxRenderer 中间件实现布局。详情请参阅 https://hono.dev/docs/middleware/builtin/jsx-renderer
异步组件
const UserList = async () => {
const users = await fetchUsers()
return (
<ul>
{users.map((u) => (
<li>{u.name}</li>
))}
</ul>
)
}
片段
const Items = () => (
<>
<li>Item 1</li>
<li>Item 2</li>
</>
)
流式传输
import { stream, streamText, streamSSE } from 'hono/streaming'
// Basic stream
app.get('/stream', (c) => {
return stream(c, async (stream) => {
stream.onAbort(() => console.log('Aborted'))
await stream.write(new Uint8Array([0x48, 0x65]))
await stream.pipe(readableStream)
})
})
// Text stream
app.get('/stream-text', (c) => {
return streamText(c, async (stream) => {
await stream.writeln('Hello')
await stream.sleep(1000)
await stream.write('World')
})
})
// Server-Sent Events
app.get('/sse', (c) => {
return streamSSE(c, async (stream) => {
let id = 0
while (true) {
await stream.writeSSE({
data: JSON.stringify({ time: new Date().toISOString() }),
event: 'time-update',
id: String(id++)
})
await stream.sleep(1000)
}
})
})
使用 app.request() 进行测试
无需启动 HTTP 服务器即可测试端点:
// GET
const res = await app.request('/posts')
expect(res.status).toBe(200)
expect(await res.json()).toEqual({ posts: [] })
// POST with JSON
const res = await app.request('/posts', {
method: 'POST',
body: JSON.stringify({ title: 'Hello' }),
headers: { 'Content-Type': 'application/json' }
})
// POST with FormData
const formData = new FormData()
formData.append('name', 'Alice')
const res = await app.request('/users', { method: 'POST', body: formData })
// With mock env (Cloudflare Workers bindings)
const res = await app.request('/api/data', {}, { KV: mockKV, DATABASE: mockDB })
// Using Request object
const req = new Request('http://localhost/api', { method: 'DELETE' })
const res = await app.request(req)
Hono 客户端(RPC)
通过服务器和客户端共享类型,实现类型安全的 API 客户端。
重要:路由必须进行链式调用,类型推断才能正常工作。不进行链式调用时,客户端无法推断路由类型。
// Server: routes MUST be chained to preserve types
const route = app
.post('/posts', zValidator('json', schema), (c) => {
return c.json({ ok: true }, 201)
})
.get('/posts', (c) => {
return c.json({ posts: [] })
})
export type AppType = typeof route
// Client: use hc() with the exported type
import { hc } from 'hono/client'
import type { AppType } from './server'
const client = hc<AppType>('http://localhost:8787/')
const res = await client.posts.$post({ json: { title: 'Hello' } })
const data = await res.json() // fully typed
类型工具:
import type { InferRequestType, InferResponseType } from 'hono/client'
type ReqType = InferRequestType<typeof client.posts.$post>
type ResType = InferResponseType<typeof client.posts.$post, 200>
辅助工具
辅助工具是从 hono/<helper-name> 导入的实用函数:
import { getConnInfo } from 'hono/conninfo'
import { getCookie, setCookie, deleteCookie } from 'hono/cookie'
import { css, Style } from 'hono/css'
import { createFactory } from 'hono/factory'
import { html, raw } from 'hono/html'
import { stream, streamText, streamSSE } from 'hono/streaming'
import { testClient } from 'hono/testing'
import { upgradeWebSocket } from '@hono/cloudflare-workers' // or another @hono/<runtime> adapter package
可用的辅助工具:Accepts、Adapter、ConnInfo、Cookie、css、Dev、Factory、html、JWT、Proxy、Route、SSG、Streaming、Testing、WebSocket。
详情请参阅 https://hono.dev/docs/helpers/<helper-name>(使用 Accept: text/markdown 获取)。
Factory
使用 createFactory 定义应用的 Env 类型(上文的 AppEnv)一次,并在应用、中间件和处理程序之间共享:
import { createFactory } from 'hono/factory'
const factory = createFactory<AppEnv>()
// Create app (AppEnv is inherited)
const app = factory.createApp()
// Create middleware (AppEnv is inherited, no need to pass generics)
const mw = factory.createMiddleware(async (c, next) => {
await next()
})
// Create handlers separately (preserves type inference)
const handlers = factory.createHandlers(logger(), (c) => c.json({ message: 'Hello' }))
app.get('/api', ...handlers)
最佳实践
- 将处理程序内联写在路由定义中,以便正确推断路径参数类型。
- 使用
app.route()按功能组织大型应用,而不要采用 Rails 风格的控制器。 - 使用
createFactory()在应用、中间件和处理程序之间共享 Env 类型。 - 使用
c.set()/c.get()在中间件和处理程序之间传递数据。 - 链式调用验证器,以验证多个请求部分(param + query + json)。
- 导出应用类型以用于 RPC:
export type AppType = typeof routes - 使用
app.request()进行测试——无需启动服务器。
适配器
Hono 可在多种运行时上运行。默认导出适用于 Cloudflare Workers、Deno 和 Bun。在 Node.js 中,请使用 Node 适配器:
// Cloudflare Workers / Deno / Bun
export default app
// Node.js
import { serve } from '@hono/node-server'
serve(app)
自 Hono v5 起,运行时专用的辅助工具(serveStatic、upgradeWebSocket、getConnInfo、Lambda 处理程序)位于独立的包中:@hono/cloudflare-workers、@hono/bun、@hono/deno、@hono/aws-lambda、@hono/lambda-edge、@hono/netlify、@hono/vercel、@hono/service-worker。请安装对应运行时的包,并从中导入;hono/<runtime> 仅适用于 v4。hono/adapter(env()、getRuntimeKey())仍位于 hono 中。Hono v5 仅支持 ESM,且需要 Node.js 22.12 或更高版本。