API 文档生成器
从现有路由处理器代码中提取 API 文档。读取源代码,推断模式,并生成 OpenAPI 规范、TypeScript 类型和可运行的 curl 示例——这些不是根据设计意图,而是从实际实现中生成。
触发条件
激活于: "generate API docs", "OpenAPI from code", "document endpoints", "API reference", "swagger generation", "endpoint documentation", "create API types", "curl examples for API"
不用于: API 设计从零开始 --> api-architect | 无源代码的 OpenAPI 编写 --> openapi-spec-writer | API 测试 --> test-automation-expert
核心能力
- 从 Express、Next.js App Router、Next.js Pages API 路由、Fastify 和 Hono 处理器中提取路由定义
- 从 TypeScript 类型、Zod 模式或运行时验证中推断请求/响应模式
- 生成 OpenAPI 3.0/3.1 规范,包含准确的路径参数、查询字符串和请求体
- 生成与实际 API 契约匹配的 TypeScript 类型定义
- 为每个端点创建包含真实示例数据的 curl 示例
- 通过追溯中间件链来记录认证要求
- 识别速率限制配置并记录每个端点的限制
- 通过分析处理器中的 throw/return 模式来分类错误响应
- 检测未文档化的端点(存在但没有 JSDoc 或模式的路由)
框架检测
在生成任何内容之前,请先识别所使用的框架。提取策略差异很大。
Express / Express 类框架
信号:app.get(), router.post(), express.Router()
路由来源:app._router.stack 或显式的路由文件
中间件链:app.use() 的顺序对认证检测至关重要
查找路由注册模式:
router.get('/users/:id', authenticate, getUser)-- 处理器之前的中间件意味着需要认证app.use('/api/v1', rateLimiter({ max: 100 }), apiRouter)-- 速率限制器应用于子树
Next.js App Router
信号:导出 GET, POST, PUT, DELETE, PATCH 的 route.ts 文件
路由来源:app/api/ 中的文件系统约定
中间件链:目录边界处的 middleware.ts
路径参数来自文件夹名称:app/api/users/[id]/route.ts --> /api/users/{id}
Next.js Pages API 路由
信号:pages/api/**/*.ts 带有默认导出处理器
路由来源:文件系统约定
方法路由:处理器内部的 req.method switch
Fastify
信号:fastify.get(), fastify.route(), 路由选项上的 schema 属性
路由来源:使用 fastify.register() 的插件注册
模式:Fastify 路由通常已有 JSON Schema —— 提取并转换
Fastify 是最容易文档化的框架,因为路由经常声明自己的模式。在推断之前,优先提取现有的 schema.body、schema.response 和 schema.querystring。
Hono
信号:app.get(), app.post(), Hono(), zValidator()
路由来源:Hono 实例上的方法链
验证:通过 zValidator 中间件实现基于 zod 的验证
使用 zValidator 的 Hono 直接提供了 Zod 模式。转换 Zod --> JSON Schema --> OpenAPI 模式。
提取过程
步骤 1:发现路由
扫描项目以查找路由注册。不要依赖单个入口点——框架通常将路由分散到多个文件中。
# Express:查找路由文件
grep -rl "express.Router\|app.get\|app.post\|router\." src/ --include="*.ts" --include="*.js"
# Next.js App Router:查找路由处理器
find src/app/api -name "route.ts" -o -name "route.js"
# Next.js Pages:查找 API 路由
find src/pages/api -name "*.ts" -o -name "*.js"
# Fastify:查找路由注册
grep -rl "fastify\.\(get\|post\|put\|delete\|route\)" src/ --include="*.ts"
# Hono:查找路由定义
grep -rl "app\.\(get\|post\|put\|delete\|patch\)" src/ --include="*.ts"
步骤 2:提取模式
对于每个路由,识别请求和响应的形状。
模式来源的优先级顺序:
- 显式的 Zod/Joi/Yup 验证模式(可信度最高)
- 请求/响应上的 TypeScript 类型注解
- JSON Schema 声明(Fastify)
- JSDoc @param / @returns 注解
- 根据响应构造进行的运行时推断(可信度最低——标记为近似值)
使用运行时推断(选项 5)时,务必在 OpenAPI 输出中用 x-inferred: true 标记该模式,以便使用者知道它可能不完整。
步骤 3:追溯认证
遍历每个路由的中间件链以确定认证要求:
- 无认证中间件:标记为
security: [](公开) - Bearer 令牌中间件:标记为
security: [{ bearerAuth: [] }] - API 密钥中间件:标记为
security: [{ apiKeyAuth: [] }] - 会话/Cookie 认证:标记为
security: [{ cookieAuth: [] }] - 多种认证选项:在安全性数组中列出所有选项作为备选
步骤 4:分类错误
读取每个处理器并列出每个错误响应:
res.status(400).json(...)或throw new BadRequestError(...)--> 400 响应模式res.status(401)--> 401(如果存在认证中间件,即使没有显式抛出也包含)res.status(404)--> 404res.status(409)--> 409(冲突,常见于创建操作)res.status(422)--> 422(验证失败)res.status(429)--> 429(速率受限,如果检测到速率限制中间件则包含)res.status(500)--> 500(始终作为一种可能性包含)
步骤 5:生成输出
生成三个产物:
- OpenAPI 规范(YAML,非 JSON——更可读,更易于比较差异)
- TypeScript 类型(独立的
.d.ts文件,包含所有请求/响应类型) - curl 示例(每个端点一个,包含标头和示例负载)
决策点
当代码库没有验证模式时
如果处理器在没有 Zod/Joi 验证的情况下接受原始的 req.body,你必须根据使用情况推断类型。寻找:
- 属性访问模式:
req.body.name,req.body.email-->{ name: string, email: string } - 解构赋值:
const { name, email } = req.body--> 同样的推断 - 数据库插入调用:
db.users.create({ name: body.name })--> 如果可用,交叉引用数据库模式
用警告标记整个规范:在信息级别使用 x-schemas-inferred: true。
当路由使用动态路径段时
- Express:
:id--> OpenAPI{id} - Next.js:
[id]--> OpenAPI{id} - Next.js 兜底路由:
[...slug]--> OpenAPI{slug},带有关数组行为的说明 - Fastify:
:id--> OpenAPI{id} - Hono:
:id--> OpenAPI{id}
当响应形状因状态码而异时
为每个状态码记录其自己的模式。如果 200 和 201 的模式不同,不要将它们合并为一个模式。常见模式:
responses:
'200':
description: 用户已找到
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: 用户未找到
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
当速率限制因端点而异时
使用 OpenAPI 扩展记录速率限制:
x-rate-limit:
requests: 100
window: 60s
scope: per-api-key
如果速率限制器是全局的(在应用级别应用一次),则在顶层 info 部分记录。如果是每个路由的,则在每个操作上记录。
TypeScript 类型生成
生成与 OpenAPI 规范完全匹配的类型。当代码库使用品牌化类型时,为 ID 使用品牌化类型。
// 从 OpenAPI 规范生成——请勿手动编辑
export interface CreateUserRequest {
name: string;
email: string;
role?: 'admin' | 'member' | 'viewer';
}
export interface CreateUserResponse {
id: string;
name: string;
email: string;
role: 'admin' | 'member' | 'viewer';
createdAt: string; // ISO 8601
}
export interface ErrorResponse {
error: {
code: string;
message: string;
details?: Array<{
field: string;
issue: string;
}>;
};
}
export interface PaginatedResponse<T> {
data: T[];
meta: {
page: number;
perPage: number;
total: number;
hasMore: boolean;
};
}
Curl 示例生成
为每个端点生成一个 curl。使用逼真但明显是伪造的数据。
# GET /api/users/:id -- 获取单个用户
curl -X GET https://api.example.com/api/users/usr_abc123 \
-H "Authorization: Bearer sk_test_..." \
-H "Accept: application/json"
# POST /api/users -- 创建新用户
curl -X POST https://api.example.com/api/users \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Ada Lovelace",
"email": "[email protected]",
"role": "member"
}'
# GET /api/users -- 列出用户(带分页)
curl -X GET "https://api.example.com/api/users?page=1&perPage=20&role=admin" \
-H "Authorization: Bearer sk_test_..." \
-H "Accept: application/json"
curl 示例的规则:
- 始终包含完整 URL(使用
https://api.example.com作为基础) - 包含所有必需的标头
- 对请求体使用
-d和格式化的 JSON - 对包含查询参数的 URL 使用引号
- 即使对于 GET 也显式显示 HTTP 方法
- 对认证令牌使用
sk_test_...(明显是伪造的) - 使用逼真的字段值,而不是 "string" 或 "test"
反模式
1. 记录尚未实现的 API
症状:规范描述了代码中尚不存在的端点 修复:仅记录代码实际实现的内容。如果计划中的端点在注释或规范文件中但不在处理器中,则排除它。
2. 忽略中间件副作用
症状:文档说端点是公开的,但实际上它需要父级中间件中的认证
修复:追溯从应用根到处理器的完整中间件链。路由定义之上的 router.use(auth) 意味着该路由之下的所有路由都需要认证。
3. 假设请求体形状
症状:因为没有验证,将 req.body 记录为 any 或 object
修复:根据使用情况推断(属性访问、解构、数据库调用)。标记为 x-inferred: true。建议添加 Zod 验证。
4. 扁平化的错误文档
症状:每个端点列出相同的通用 "400 Bad Request",没有详细信息 修复:读取每个处理器的错误路径。记录返回的具体错误代码和消息。不同的验证失败应显示不同的示例响应。
5. 过时的生成文档
症状:规范只生成一次,并且从未随代码变更而更新
修复:生成到已知路径(docs/openapi.yaml)。添加 CI 检查,以便在生成规范并与已提交的规范进行比较。如果它们不一致,则构建失败。
6. 缺少分页文档
症状:列出的端点没有记录分页的查询参数模式
修复:如果处理器支持 page、limit、cursor、offset 或类似参数,请使用默认值和最大值记录它们。
7. 未记录的文件上传
症状:将 multipart/form-data 端点记录为 JSON
修复:检测 multer、formidable、busboy 或框架原生的文件处理。使用 multipart/form-data 内容类型和适当的二进制模式。
质量检查清单
[ ] 代码库中的每个路由处理器都有相应的 OpenAPI 操作
[ ] 代码和规范之间的路径参数匹配(没有 :id 与 {userId} 不匹配的情况)
[ ] 请求体模式涵盖了所有必需和可选字段
[ ] 响应模式与实际的 JSON 结构匹配(通过读取处理器返回进行验证)
[ ] 认证要求与中间件链分析相匹配
[ ] 在存在速率限制中间件的地方记录了速率限制信息
[ ] 错误响应已根据处理器中的实际 throw/return 语句进行分类
[ ] Curl 示例在运行实例上成功执行
[ ] TypeScript 类型编译无误
[ ] 没有未记录的 x-inferred 模式(每个标记的模式都有一个 TODO 用于添加适当的验证)
[ ] 分页参数已记录默认值和最大值
[ ] 文件上传端点使用 multipart/form-data 内容类型
[ ] OpenAPI 规范通过 spectral 或 swagger-cli lint 验证
[ ] 生成的规范已提交到已知路径,用于 CI 差异比较
输出产物
docs/openapi.yaml-- 完整的 OpenAPI 3.0/3.1 规范docs/api-types.d.ts-- 所有请求/响应形状的 TypeScript 类型定义docs/api-examples.sh-- 每个端点的可运行 curl 示例docs/api-coverage.md-- 列出已记录和未记录路由的报告
验证
# Lint 生成的 OpenAPI 规范
npx @stoplight/spectral-cli lint docs/openapi.yaml
# 验证规范结构
npx swagger-cli validate docs/openapi.yaml
# 生成 TypeScript 客户端以验证类型是否可编译
npx openapi-typescript docs/openapi.yaml -o /tmp/api-check.d.ts
tsc --noEmit /tmp/api-check.d.ts
# 与上次提交的规范进行比较(CI 检查)
diff <(cat docs/openapi.yaml) <(npx tsx scripts/generate-api-docs.ts --stdout) && echo "规范是最新的" || echo "规范已过时"
涵盖: OpenAPI 生成 | TypeScript 类型提取 | Curl 示例创建 | 认证文档 | 速率限制文档 | 错误代码分类
与以下工具配合使用: api-architect(先设计,后文档化) | openapi-spec-writer(手动编写) | test-automation-expert(根据生成的规范进行契约测试)