mcpField
H
hono

hono

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

关于 hono

Hono Skill 为构建 Hono Web 应用提供 AI 可用的内联参考,涵盖路由、中间件、验证和测试等 API,并说明如何用 CLI 测试端点。

主要功能

  1. 01Hono API 参考
  2. 02路由与中间件指南
  3. 03请求验证示例
  4. 04使用 hono request 测试端点
  5. 05类型安全的 Hono Client RPC
  6. 06JSX 与流式传输示例

适用场景

  1. 01在 Hono 中构建路由和中间件
  2. 02无需启动服务器即可测试端点
  3. 03为 Hono API 创建类型化客户端

MCP 相关

若配置了 hono-docs MCP 服务,该 Skill 建议使用其工具获取最新文档。

安装

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

$npx skills add https://github.com/marcioaltoe/skills --skill hono

安装后在 Claude Code 中输入/hono确认已加载。

附带文件agents/openai.yaml

详细介绍

Hono 技能

构建 Hono Web 应用。此技能为 AI 提供内联 API 知识。使用 npx hono request 测试端点。如果已配置 hono-docs MCP 服务,请优先使用其工具获取最新文档,而非内联参考。

Hono CLI 用法

请求测试

无需启动 HTTP 服务即可测试端点。内部使用 app.request()。

# GET request
npx hono request [file] -P /path

# POST request with JSON body
npx hono request [file] -X POST -P /api/users -d '{"name": "test"}'

注意: 不要直接在 CLI 参数中传递凭据。敏感值请使用环境变量。hono request 不支持 Cloudflare Workers 绑定(KV、D1、R2 等)。需要绑定时,请改用 workers-fetch:

npx workers-fetch /path
npx workers-fetch -X POST -H "Content-Type:application/json" -d '{"name":"test"}' /api/users

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)
})

Context(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 { csrf } from 'hono/csrf'
import { ipRestriction } from 'hono/ip-restriction'
import { bodyLimit } from 'hono/body-limit'
import { requestId } from 'hono/request-id'
import { methodOverride } from 'hono/method-override'
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 中间件处理布局。详情请参阅 npx hono docs /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。

详情请使用 npx hono docs /docs/helpers/<helper-name>。

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