关于 hono
这是一项规定使用 Hono 构建 HTTP API 的编码技能,涵盖路由与 OpenAPI 设计、校验、错误、安全、健康检查、中间件顺序和 MCP 服务配置。
主要功能
- 01统一使用 Hono
- 02从路由和模式生成 OpenAPI
- 03规定输入校验与错误处理
- 04制定认证和健康检查规则
- 05说明中间件顺序及 MCP 适配器
- 06加入 OpenAPI 规范检查
适用场景
- 01新建或审查 Hono HTTP API
- 02生成并校验 OpenAPI 文档
- 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 形式或中间件参数,不要依赖记忆,请查阅以下文档。
- 文档 https://hono.dev/docs/
- Hono 对象与路由 https://hono.dev/docs/api/hono
- HTTPException https://hono.dev/docs/api/exception
- 中间件的机制与顺序 https://hono.dev/docs/guides/middleware
- 验证 https://hono.dev/docs/guides/validation
- bearer-auth https://hono.dev/docs/middleware/builtin/bearer-auth
- cors https://hono.dev/docs/middleware/builtin/cors
- secure-headers https://hono.dev/docs/middleware/builtin/secure-headers
- zod-openapi 示例 https://hono.dev/examples/zod-openapi
- @hono/zod-openapi https://github.com/honojs/middleware/tree/main/packages/zod-openapi
- @hono/mcp https://github.com/honojs/middleware/tree/main/packages/mcp
- 发布与破坏性变更 https://github.com/honojs/hono/releases
规范的一手来源。
- RFC 9457 Problem Details https://www.rfc-editor.org/rfc/rfc9457.html
- Health Check Response Format https://datatracker.ietf.org/doc/html/draft-inadarei-api-health-check
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 的粒度。
中间件顺序
中间件按从上到下的顺序应用。顺序错误会导致处理器在认证前运行。
- requestId、logger。
- secureHeaders、cors。
- bearerAuth 等认证。
- 各路由单独进行验证。
- 处理器。
- 最后注册 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?