mcpField
H
hono

hono

github.com/honojs开发173.1k 次安装2 周前更新
代码仓库

关于 hono

hono 是面向 AI 的 Hono 开发技能,提供 API 参考,并指导使用 Hono CLI 检查和测试应用。

主要功能

  1. 01内嵌 Hono API 参考
  2. 02指导获取最新文档
  3. 03使用 CLI 检查路由
  4. 04无服务器请求与批量测试
  5. 05捕获并回归测试响应

适用场景

  1. 01创建或修改 Hono 路由
  2. 02在不启动服务器时测试端点
  3. 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(cf CLI)和 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 或更高版本。

评论

支持 Markdown