mcpField
H
hono

hono

github.com/philtzjp开发2506 次安装4 天前更新
代码仓库

关于 hono

这是一项规定使用 Hono 构建 HTTP API 的编码技能,涵盖路由与 OpenAPI 设计、校验、错误、安全、健康检查、中间件顺序和 MCP 服务配置。

主要功能

  1. 01统一使用 Hono
  2. 02从路由和模式生成 OpenAPI
  3. 03规定输入校验与错误处理
  4. 04制定认证和健康检查规则
  5. 05说明中间件顺序及 MCP 适配器
  6. 06加入 OpenAPI 规范检查

适用场景

  1. 01新建或审查 Hono HTTP API
  2. 02生成并校验 OpenAPI 文档
  3. 03检查 API 安全与错误响应

MCP 相关

使用 Hono 构建 MCP 服务时,技能建议采用 @hono/mcp 适配器,而非另设 HTTP 服务。

安装

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

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

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

详细介绍

hono

方针

项目内统一使用 Hono 作为 HTTP 框架。同一项目中不要混用多个 HTTP 框架。

从一个位置生成路由定义、架构和 OpenAPI 文档。不要将手写的 OpenAPI YAML 与实现分开维护。双重维护必然会出现偏差。

Hono 更新很快。如果不确定 API 形式或中间件参数,不要依赖记忆,请查阅以下文档。

规范的一手来源。

URL 设计

  • 使用路径版本控制。放在 /api/v1/ 下。
  • 路径尽可能简短。不得已需要多个词时,使用 kebab-case。
  • 名词使用单数形式。使用 /user,而不是 /users。
  • 路径参数采用 /user/{id} 的形式。与 OpenAPI 的表示方式保持一致。
import { Hono } from 'hono'

const api = new Hono().basePath('/api/v1')

OpenAPI

使用 @hono/zod-openapi,从路由和架构生成 OpenAPI 文档。

import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi'

const UserSchema = z.object({
  id: z.string().openapi({ example: '01JABC' }),
  displayName: z.string().max(50),
}).openapi('User')

const ProblemSchema = z.object({
  type: z.string(),
  title: z.string(),
  status: z.number(),
  detail: z.string().optional(),
}).openapi('Problem')

const getUser = createRoute({
  method: 'get',
  path: '/user/{id}',
  request: {
    params: z.object({ id: z.string() }),
  },
  responses: {
    200: {
      content: { 'application/json': { schema: UserSchema } },
      description: 'ユーザーを返す',
    },
    404: {
      content: { 'application/problem+json': { schema: ProblemSchema } },
      description: '該当するユーザーがない',
    },
  },
})

const app = new OpenAPIHono().basePath('/api/v1')

app.openapi(getUser, (c) => {
  const { id } = c.req.valid('param')
  return c.json({ id, displayName: '例' })
})

app.doc('/openapi.json', {
  openapi: '3.1.0',
  info: { title: 'Example API', version: '1.0.0' },
})
  • 在 responses 中写明成功和失败两种情况。不要省略错误响应。
  • 使用 c.req.valid() 接收输入。不要未经验证就使用原始的 c.req.json()。
  • 使用 .openapi('User') 为架构命名,并作为组件重复使用。

错误

返回 RFC 9457 的 Problem Details。Content-Type 为 application/problem+json。

import { HTTPException } from 'hono/http-exception'

app.onError((err, c) => {
  const status = err instanceof HTTPException ? err.status : 500
  const problem = {
    type: `https://example.com/problems/${status === 404 ? 'not-found' : 'internal'}`,
    title: status === 404 ? 'Resource not found' : 'Internal server error',
    status,
    instance: c.get('requestId'),
  }
  return c.body(JSON.stringify(problem), status, {
    'Content-Type': 'application/problem+json; charset=utf-8',
  })
})
  • 正文中的 status 必须与 HTTP 状态行一致。
  • detail 应说明客户端如何修正问题,而不是提供调试信息。
  • 不要暴露堆栈跟踪、内部主机名、数据库名称或用户信息。
  • 未知路径通过 app.notFound() 返回相同格式的 404。不要通过 SPA 回退返回 200。请参阅 errorpage 技能。
  • 让客户端根据 HTTP 状态和稳定的 type URI 进行分支判断,而不是根据字符串。

认证

向网络公开的 API 必须使用 Bearer 认证。

import { bearerAuth } from 'hono/bearer-auth'

app.use('/api/v1/*', bearerAuth({ verifyToken: async (token, c) => verify(token) }))

仅绑定到 loopback(127.0.0.1)的本地服务,可以使用 Origin 和 Host 标头验证来代替 Bearer 进行保护。这是防范 DNS 重绑定所必需的。

app.use('*', async (c, next) => {
  const origin = c.req.header('Origin')
  const host = c.req.header('Host')
  if (origin && !ALLOWED_ORIGINS.includes(origin)) return c.body(null, 403)
  if (!host || !/^(127\.0\.0\.1|\[::1\]|localhost)(:\d+)?$/.test(host)) return c.body(null, 403)
  await next()
})
  • 不要通过中间件统一处理授权,必须在处理器或服务层确认资源所有权。
  • 不要将令牌验证、所有权确认和速率限制交由客户端输入决定。

健康检查

遵循 draft-inadarei-api-health-check。Content-Type 为 application/health+json。

app.get('/health', (c) =>
  c.body(JSON.stringify({
    status: 'pass',
    version: '1',
    releaseId: process.env.RELEASE_ID,
    checks: {
      'db:responseTime': [{ componentType: 'datastore', status: 'pass' }],
    },
  }), 200, { 'Content-Type': 'application/health+json; charset=utf-8' }))
  • status 必须是 pass、warn 或 fail 之一。
  • 不要将其放在认证之后,否则监控将无法访问。
  • 不要暴露依赖项的详细内部信息。确定公开范围后,再决定 checks 的粒度。

中间件顺序

中间件按从上到下的顺序应用。顺序错误会导致处理器在认证前运行。

  1. requestId、logger。
  2. secureHeaders、cors。
  3. bearerAuth 等认证。
  4. 各路由单独进行验证。
  5. 处理器。
  6. 最后注册 onError 和 notFound。

cors 只列出必需的来源。不要同时使用通配符和 credentials。

MCP 服务

如果要搭建 MCP 服务,请使用与所选框架兼容的适配器。Hono 使用 @hono/mcp。不要并行搭建另一个 HTTP 服务。

代码检查

npx --yes @stoplight/spectral-cli lint openapi.json

在 .spectral.yaml 中设置 extends: ["spectral:oas"]。针对生成的 OpenAPI 文档运行,并将其集成到 CI 中。不要通过禁用规则来压制问题;应从设计层面修正问题。

确认前检查

  • HTTP 框架是否已统一为 Hono?是否混用了其他框架?
  • OpenAPI 文档是否由实现生成?是否还单独保留了手写定义?
  • 路径是否位于 /api/v1/ 下,并使用单数形式和 kebab-case?
  • 所有输入是否都经过 c.req.valid()?
  • 错误是否使用 application/problem+json,且正文中的 status 是否与 HTTP 状态一致?
  • 错误正文中是否泄露了内部信息?
  • 公开 API 是否使用 Bearer 认证?loopback 服务是否验证 Origin 和 Host?
  • 是否在处理器或服务层确认了所有权?
  • /health 是否无需认证即可访问,并返回 application/health+json?
  • 中间件注册顺序是否正确?onError 和 notFound 是否最后注册?
  • 是否在 CI 中运行 Spectral?

评论

支持 Markdown