mcpField
H
hono

hono

github.com/pedronauck开发647292 次安装1 天前更新
代码仓库

关于 hono

hono 是用于构建 Hono Web 应用的 AI Skill,提供内置 API 指引,并引导编码 Agent 使用 Hono CLI 检查和测试应用。

主要功能

  1. 01内置 Hono API 参考
  2. 02通过 CLI 检查和测试应用
  3. 03无需启动服务即可测试请求与批处理
  4. 04使用路由快照进行回归检查
  5. 05提供验证、JSX、流式处理和 RPC 指引

适用场景

  1. 01构建或修改 Hono 路由
  2. 02测试请求流程和预期响应
  3. 03修改前检查现有路由行为

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/pedronauck/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 检查和测试应用。在项目中安装,然后让 CLI 说明自身用法:

npm install -D @hono/cli@next
npx hono agent-context

按照输出操作。它会说明每个命令(routes、request、batch、snapshot、benchmark、optimize、ssg)、JSON 输出约定和工作流。

注意:

  • hono request 使用 app.request() 发送请求,无需服务器。不要直接在 CLI 参数中传递凭据;敏感值请使用环境变量。

  • 对于 Cloudflare Workers 绑定(KV、D1、R2 等),使用 hono request /path --runtime workerd。它会使用项目的 wrangler 配置启动应用,因此本地绑定(c.env)是真实可用的。项目中必须安装 wrangler。

  • 如果要发送多个请求,或运行需要保留状态的流程(先 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 Env = {
  Bindings: { DATABASE: D1Database; KV: KVNamespace }
  Variables: { user: User }
}
const app = new Hono<Env>()
路由方法
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)
const value = await c.env.KV.get('key')
const db = c.env.DATABASE
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 as object
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() // parse JSON body
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

设置

在 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 other adapter

可用的辅助工具: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,并在应用、中间件和处理程序之间共享:

import { createFactory } from 'hono/factory'

const factory = createFactory<Env>()

// Create app (Env type is inherited)
const app = factory.createApp()

// Create middleware (Env type 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)

评论

支持 Markdown