在小团队里,最扯淡的协作方式就是前端端着一个过期半个月的 Markdown 文档去问后端:“为什么这个字段返回的是 null,而且文档里根本没有这个字段?”后端一拍脑门:“哦,我上周重构数据库把这个字段删了,忘了改 Markdown 了。”这种名存实亡的文档库不仅没有减少沟通成本,反而天天在制造信任危机和无谓的内耗。
手写接口文档是反人类的。代码是动态演进的,而手写的 Markdown 是静态的,靠人肉去同步这两者,迟早会翻车。
解决这个问题的唯一标准就是:代码即文档。我们需要一个“单一真理源”(Single Source of Truth),让接口定义直接来源于后端代码,并且自动生成 Swagger UI 以及前端可以直接引用的 TypeScript 类型定义。
我们现在的核心工作流是:后端用 tsoa 或者 Nest.js 的 Swagger 装饰器来写接口,或者使用 zod-openapi 这种方案,通过代码声明直接输出符合 OpenAPI 3.0 规范的 JSON 文件。前端则通过 CLI 工具直接读取这个 JSON 网址,自动生成 Axios/Fetch 请求方法和严谨的 TypeScript 接口类型。
这里分享我们目前基于 Express + zod-to-openapi 实现的轻量级 API 自文档方案,完美契合 TypeScript 全栈开发:
import express from 'express';
import { OpenAPIRegistry, OpenAPIGeneratorV3 } from '@asteasolutions/zod-to-openapi';
import * as swaggerUi from 'swagger-ui-express';
import { z } from 'zod';
const registry = new OpenAPIRegistry();
// 1. 定义数据 Schema,自动注入 OpenAPI 元数据
const UserSchema = registry.register('User', z.object({
id: z.string().openapi({ example: 'usr_1001' }),
nickname: z.string().openapi({ example: '天马' }),
email: z.string().email().openapi({ example: 'tianma@example.com' }),
}));
// 2. 注册接口路由的定义
registry.registerPath({
method: 'get',
path: '/users/{id}',
summary: '获取用户详细信息',
request: {
params: z.object({
id: z.string().openapi({ example: 'usr_1001' }),
}),
},
responses: {
200: {
description: '成功返回用户信息',
content: {
'application/json': {
schema: UserSchema,
},
},
},
},
});
// 3. 生成 OpenAPI 文档对象
function generateOpenApiDocs() {
const generator = new OpenAPIGeneratorV3(registry.definitions);
return generator.generateDocument({
openapi: '3.0.0',
info: {
title: '小团队核心业务 API',
version: '1.0.0',
description: '由后端代码自动生成的单一真理源文档,严禁人肉修改',
},
servers: [{ url: '/api' }],
});
}
// 4. 挂载到 Express 服务中
const app = express();
const openApiDocument = generateOpenApiDocs();
// 暴露 JSON 规范端点,供前端自动化工具(如 openapi-typescript)消费
app.get('/swagger.json', (req, res) => {
res.setHeader('Content-Type', 'application/json');
res.send(openApiDocument);
});
// 挂载 Swagger UI 交互界面
app.use('/docs', swaggerUi.serve, swaggerUi.setup(openApiDocument));
app.listen(3000, () => {
console.log('Document service is running on http://localhost:3000/docs');
});
前端只需要在项目里配置一个 npm script,比如 openapi-typescript http://localhost:3000/swagger.json --output ./src/types/api.ts。每次后端接口有变动,前端开发只需跑一下这个命令,本地的 TypeScript 类型就会瞬间更新。如果后端删了字段或者改了类型,前端的 IDE 会立刻飘红报错,连编译都过不去。
这套流转机制把原本脆弱的人肉沟通,彻底变成了编译期的强约束。后端省去了写文档的痛苦,前端拿到的永远是最新的类型,撕逼和排查时间起码减少了 80%。
别再当 Markdown 搬运工了,赶紧花半天时间把 OpenAPI 工具链搭起来,早点下班回家它不香吗?