Koa.js API 文档自动生成与 OpenAPI 实践
引言
良好的 API 文档是前后端协作的基础。本文介绍如何在 Koa.js 项目中实现 API 文档的自动生成,基于 OpenAPI 3.0 规范,支持 Swagger UI 可视化展示和代码生成。
技术选型
| 工具 | 功能 | 特点 |
|---|---|---|
| koa-swagger-decorator | 注解方式自动生成 | 装饰器风格,代码简洁 |
| swagger-jsdoc | JSDoc 注释解析 | 无需装饰器,兼容性好 |
| @koa/swagger | 中间件方式集成 | 运行时生成,动态更新 |
环境安装
# 安装依赖 npm install koa-swagger-decorator swagger-ui-koa # 或使用组合方案 npm install koa2-swagger-ui swagger-jsdoc
decorators 方案实现
使用装饰器方式定义 API 文档:
// swagger.js - Swagger 配置
import { setMeta, swagger, updateOpenApi } from 'koa-swagger-decorator';
export const swaggerOptions = {
title: 'Koa.js REST API',
version: '1.0.0',
description: '企业信息化系统 API 文档',
definition: {
openapi: '3.0.0',
info: {
title: '企业信息化系统 API',
version: '1.0.0',
description: 'EIMS 系统 RESTful API 接口文档',
contact: {
name: 'API Support',
email: 'webnic@qq.com'
}
},
servers: [
{
url: 'http://localhost:3000',
description: '开发环境'
},
{
url: 'https://api.eims.com',
description: '生产环境'
}
],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT'
}
}
},
security: [{ bearerAuth: [] }]
}
};
// 在应用启动时初始化
export function initSwagger(router) {
swagger(swaggerOptions)(router);
return router;
}
// user.controller.js - 用户接口
import {
controller,
Get,
Post,
Put,
Delete,
Params,
Query,
Body,
Summary,
Description,
Tags,
Middleware,
Responses
} from 'koa-swagger-decorator';
import { validateBody, validateParams } from '../middleware/validation';
import { authenticate } from '../middleware/auth';
// 定义请求/响应 DTO
const UserDTO = {
id: { type: 'number', required: false },
username: { type: 'string', required: true, example: 'admin' },
email: { type: 'string', required: true, example: 'admin@example.com' },
role: { type: 'string', enum: ['admin', 'user', 'guest'], default: 'user' },
createdAt: { type: 'string', format: 'date-time' }
};
const CreateUserSchema = {
username: { type: 'string', minLength: 3, maxLength: 50 },
email: { type: 'string', format: 'email' },
password: { type: 'string', minLength: 6 }
};
@Tags('用户管理')
@Middleware(authenticate)
@controller('/api/users')
export class UserController {
@Summary('获取用户列表')
@Description('获取所有用户,支持分页和筛选')
@Get('/')
@Responses(200, '返回用户列表')
@Responses(401, '未授权')
async getUsers(ctx) {
const { page = 1, pageSize = 10, role } = ctx.query;
const users = await UserService.findAll({ page, pageSize, role });
const total = await UserService.count({ role });
ctx.body = {
code: 0,
data: {
list: users,
pagination: { page, pageSize, total }
}
};
}
@Summary('获取用户详情')
@Description('根据用户ID获取详细信息')
@Get('/:id')
@Responses(200, '返回用户详情')
@Responses(404, '用户不存在')
async getUserById(@Params('id') id: number) {
const user = await UserService.findById(id);
if (!user) {
ctx.throw(404, 'User not found');
}
ctx.body = { code: 0, data: user };
}
@Summary('创建用户')
@Description('创建新用户,需要管理员权限')
@Post('/')
@Responses(201, '创建成功')
@Responses(400, '参数错误')
async createUser(@Body() body: typeof CreateUserSchema) {
// 验证参数
await validateBody(CreateUserSchema)(ctx);
const user = await UserService.create(body);
ctx.status = 201;
ctx.body = { code: 0, data: user };
}
@Summary('更新用户')
@Description('更新用户信息')
@Put('/:id')
@Responses(200, '更新成功')
@Responses(403, '无权限')
async updateUser(@Params('id') id: number, @Body() body: Partial) {
const user = await UserService.update(id, body);
ctx.body = { code: 0, data: user };
}
@Summary('删除用户')
@Description('删除指定用户(软删除)')
@Delete('/:id')
@Responses(204, '删除成功')
async deleteUser(@Params('id') id: number) {
await UserService.softDelete(id);
ctx.status = 204;
}
@Summary('修改密码')
@Description('用户修改自己的密码')
@Post('/:id/password')
@Responses(200, '修改成功')
@Responses(400, '密码错误')
async changePassword(
@Params('id') id: number,
@Body() body: { oldPassword: string; newPassword: string }
) {
const { oldPassword, newPassword } = body;
// 验证旧密码
const valid = await UserService.verifyPassword(id, oldPassword);
if (!valid) {
ctx.throw(400, 'Old password is incorrect');
}
await UserService.updatePassword(id, newPassword);
ctx.body = { code: 0, message: 'Password updated successfully' };
}
}
路由注册与 Swagger UI
// app.js - 应用入口
import Koa from 'koa';
import Router from 'koa-router';
import {mount} from 'koa-mount';
import {swaggerKoa} from 'koa-swagger-decorator';
import {initSwagger} from './swagger';
const app = new Koa();
const router = new Router();
// 初始化 Swagger
initSwagger(router);
// 挂载 Swagger UI
router.get('/api-docs', swaggerKoa());
// 注册路由
import {UserController} from './controllers/user.controller';
router.applyRoutes(UserController);
// 中间件顺序很重要
app.use(router.routes());
app.use(router.allowedMethods());
// 静态文件(Swagger UI)
app.use(mount('/swagger', KoaStatic(path.join(__dirname, '..', 'node_modules', 'swagger-ui-dist'))));
console.log('Swagger UI: http://localhost:3000/api-docs');
JSDoc 方案实现
对于不想使用装饰器的项目,可以使用 JSDoc 注释:
// user.service.js - 使用 JSDoc 注释
const router = require('koa-router')();
/**
* @openapi
* /api/users:
* get:
* summary: 获取用户列表
* tags:
* - 用户管理
* parameters:
* - name: page
* in: query
* schema:
* type: integer
* default: 1
* - name: pageSize
* in: query
* schema:
* type: integer
* default: 10
* responses:
* 200:
* description: 成功
* content:
* application/json:
* schema:
* type: object
* properties:
* code:
* type: integer
* data:
* type: object
*/
router.get('/api/users', async ctx => {
// ...
});
module.exports = router;
OpenAPI 规范扩展
自定义 OpenAPI 组件定义可复用的 schema:
// schema.js - 通用 Schema 定义
export const schemas = {
// 错误响应
ErrorResponse: {
type: 'object',
properties: {
code: { type: 'integer', example: 400 },
message: { type: 'string', example: '请求参数错误' },
details: { type: 'object' }
}
},
// 分页响应
PaginatedResponse: {
type: 'object',
properties: {
list: {
type: 'array',
items: { type: 'object' }
},
pagination: {
type: 'object',
properties: {
page: { type: 'integer' },
pageSize: { type: 'integer' },
total: { type: 'integer' },
totalPages: { type: 'integer' }
}
}
}
},
// 订单模型
Order: {
type: 'object',
required: ['orderNo', 'amount'],
properties: {
id: { type: 'integer' },
orderNo: { type: 'string' },
amount: { type: 'number' },
status: { type: 'string', enum: ['pending', 'paid', 'shipped', 'completed'] },
items: {
type: 'array',
items: { $ref: '#/components/schemas/OrderItem' }
}
}
}
};
代码生成与测试
使用 OpenAPI 规范生成客户端代码:
# 使用 openapi-generator 生成客户端 npx @openapitools/openapi-generator-cli generate \ -i http://localhost:3000/api-docs/swagger.json \ -g typescript-axios \ -o ./src/api-client # 生成服务端 stub npx @openapitools/openapi-generator-cli generate \ -i http://localhost:3000/api-docs/swagger.json \ -g koa-server \ -o ./src/server-stub # 生成 Postman 集合 npx @openapitools/openapi-generator-cli generate \ -i http://localhost:3000/api-docs/swagger.json \ -g postman \ -o ./docs/postman.json # 生成 Markdown 文档 npx @openapitools/openapi-generator-cli generate \ -i http://localhost:3000/api-docs/swagger.json \ -g markdown \ -o ./docs
总结
Koa.js 项目中实现 API 文档自动化的核心要点:
- 选择方案:装饰器方案更现代,JSDoc 方案更灵活
- 规范优先:使用 OpenAPI 3.0 规范保���兼容性
- 文档即代码:将文档定义与代码放在一起,版本同步
- 自动化:CI/CD 中自动生成和发布文档
- 代码生成:利用规范生成客户端代码,提升开发效率
良好的 API 文档不仅便于前后端协作,还能作为契约测试和自动化的基础。