API文档的多版本共存问题,本质上是接口迭代速度与文档维护成本之间的矛盾。当一个项目同时服务于老客户端和新客户端时,v1接口不能删,v2接口要上线,两套文档如果靠手工维护,迟早会出现文档与代码不一致的情况。用Node.js配合自动化工具链,可以让文档随着代码一起构建、一起部署,多个版本互不干扰。下面从版本隔离设计、自动化生成、多版本部署三个方面展开。

一、基于路由前缀的版本隔离设计
多版本共存的第一步是在路由层面把版本切开。Express和Koa都支持通过中间件挂载的方式实现路径前缀隔离,核心思路是每个版本一个Router实例,各版本内部结构完全独立。这样v1的接口签名即使被v2重写,也不会影响v1的文档生成。
const express = require('express');
const app = express();
const v1Router = require('./routes/v1');
const v2Router = require('./routes/v2');
// 按前缀挂载,版本之间完全隔离
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);
app.listen(3000, () => {
console.log('服务已启动,监听3000端口');
});目录结构上建议按版本分文件夹组织,例如routes/v1、routes/v2、docs/v1、docs/v2。这种物理隔离的好处是显而易见的:代码回溯、文档生成、权限控制都可以按版本独立操作。v1进入维护期时,只需要冻结routes/v1目录,v2照常开发,代码评审时责任边界也非常清晰。
需要注意一点,版本号一旦发布就不要轻易改动路径结构。有些团队喜欢用header传版本号,这种方式在网关层做灰度时有用,但对文档系统不友好,因为文档工具默认按URL路径区分版本。除非有特殊需求,否则路径前缀仍是多版本文档管理的首选方案。
二、从代码注释自动生成各版本文档
文档自动化的核心是把接口定义写进代码,构建时抽取生成文档,而不是事后手写。在Node.js生态里,swagger-jsdoc是最常用的方案,它通过扫描JSDoc注释生成OpenAPI规范文件。配合多版本架构,每个版本各自维护一份配置,产物自然分离。
// docs/v1/swagger.config.js
const swaggerJsdoc = require('swagger-jsdoc');
const options = {
definition: {
openapi: '3.0.0',
info: {
title: '用户服务 API',
version: '1.9.2',
description: 'v1版本文档,当前处于维护期,仅修复严重缺陷'
}
},
// 只扫描v1目录下的注释
apis: ['./routes/v1/*.js']
};
module.exports = swaggerJsdoc(options);接口注释直接写在路由文件里,路由和文档永远在同一处修改,这是防止文档过期最有效的手段。注释示例如下:
// routes/v1/user.js
/**
* @openapi
* /user/{id}:
* get:
* summary: 获取用户信息
* tags: [用户管理]
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: 返回用户基础信息
*/
router.get('/user/:id', handler);生成环节可以写成一个脚本,配合npm scripts在每次构建或发版时执行,把OpenAPI JSON输出到docs目录,再交给前端文档站渲染。脚本本身很简单:
// scripts/build-docs.js
const fs = require('fs');
const specV1 = require('../docs/v1/swagger.config');
const specV2 = require('../docs/v2/swagger.config');
fs.mkdirSync('./public/docs', { recursive: true });
fs.writeFileSync('./public/docs/v1.json', JSON.stringify(specV1));
fs.writeFileSync('./public/docs/v2.json', JSON.stringify(specV2));
console.log('多版本文档构建完成');这种做法的最大优势是文档即代码。版本迭代时v2的开发者不用关心v1的文档长什么样,反之亦然,每个人只维护自己版本的注释即可。缺点是注释规范需要团队约定,建议在ESLint或代码评审环节加一道检查,防止有人漏写注释导致文档缺失。
三、多版本文档站的部署与版本废弃策略
文档生成之后需要一个统一的入口展示。常见做法是用swagger-ui-express把多个版本的文档挂到不同路径,服务启动后访问对应的地址就能看到对应版本的交互式文档:
const swaggerUi = require('swagger-ui-express');
const specV1 = require('./docs/v1/swagger.config');
const specV2 = require('./docs/v2/swagger.config');
app.use('/docs/v1', swaggerUi.serve, swaggerUi.setup(specV1));
app.use('/docs/v2', swaggerUi.serve, swaggerUi.setup(specV2));
// 根路径提供版本索引页
app.get('/docs', (req, res) => {
res.json({
versions: [
{ version: 'v2', status: 'current', path: '/docs/v2' },
{ version: 'v1', status: 'deprecated', sunset: '2025-06-30', path: '/docs/v1' }
]
});
});版本废弃策略同样重要。老版本不能无声无息地消失,正确做法是在文档首页和接口响应头里同时声明废弃状态。响应头可以加一个Deprecation标记和Sunset字段告知客户端下线时间,同时通过中间件在文档描述中自动追加废弃提示,让接入方有足够的时间迁移。
最后,如果团队项目较多,可以把这套方案沉淀成内部的文档模板工程:统一的注释规范、统一的构建脚本、统一的部署流水线。新项目初始化时直接继承这套结构,文档多版本共存就不再依赖某个人的自觉,而是流程自动保证的结果。CI环节中每次合并代码自动重建文档并部署,人工只需要关注注释本身的质量,这才是自动化文档体系的最终形态。