Node.js Mock 服务依赖包安装清单
mock 包 yarn add yamljs cors morgan faker -S yarn add @types/faker @types/yamljs @types/cors @types/morgan @types/compression -D
swagger-routes-express ts-node-dev
这套包各自负责什么
这几个包凑在一起是为了做一件事:拿一份 OpenAPI(Swagger)文档当唯一来源,自动生成一个能跑的 mock 服务。分工是这样的:
yamljs:把openapi.yaml读成 JS 对象。OpenAPI 文档一般写成 YAML,Node 不能直接require它。swagger-routes-express:核心。吃进 OpenAPI 对象,按里面每个 path 的operationId去你的 controller 对象里找同名函数并挂上路由。路由表不用手写,改文档就等于改路由。faker:生成假数据(姓名、地址、日期、图片 URL、UUID 等),比手写死数据像真的。cors:前端跑在另一个端口,不加这个所有请求都被浏览器的同源策略拦住。morgan:请求日志。mock 服务调试时最需要的就是「前端到底打了什么请求过来」。compression:gzip 压缩响应。mock 环境其实可有可无,装它主要是为了贴近真实服务的行为。ts-node-dev:改 TS 文件自动重启服务,不用手动tsc+node。@types/*:上面这些包本身用 JS 写的,类型定义单独发布在@types下。
两个包已经不该用了
faker 这个包在 2022 年 1 月被作者自行清空并发布了 6.6.6 空版本,现在装到的可能是个不可用的版本。社区接管后的正式包名是 @faker-js/faker,自带 TS 类型,不需要再装 @types/faker:
yarn remove faker @types/faker
yarn add -D @faker-js/faker
API 也做了整理,主要变化是命名空间从 faker.name 改成 faker.person:
import { faker } from '@faker-js/faker/locale/zh_CN';
const user = {
id: faker.string.uuid(),
name: faker.person.fullName(),
email: faker.internet.email(),
avatar: faker.image.avatar(),
createdAt: faker.date.past(),
};
从 locale 子路径导入能让生成的中文姓名、地址像样,默认的 en 会给你一堆英文名。
ts-node-dev 已不再积极维护,现在更省事的是 tsx:
yarn add -D tsx
{
"scripts": {
"mock": "tsx watch src/mock/server.ts"
}
}
tsx 基于 esbuild,启动和重启都比 ts-node-dev 快一个量级,而且原生支持 ESM,不用折腾 --esm 之类的参数。Node 22+ 其实已经能直接 node --experimental-strip-types 跑 TS 了,简单脚本连 tsx 都可以省掉。
一份能跑起来的最小实现
// src/mock/server.ts
import express from 'express';
import cors from 'cors';
import morgan from 'morgan';
import compression from 'compression';
import YAML from 'yamljs';
import { connector } from 'swagger-routes-express';
import * as api from './controllers';
async function bootstrap() {
const app = express();
app.use(cors());
app.use(morgan('dev'));
app.use(compression());
app.use(express.json());
// 读 OpenAPI 文档
const apiDefinition = YAML.load('./openapi.yaml');
// 按 operationId 把文档里的 path 和 controllers 里的函数对应起来
const connect = connector(api, apiDefinition, {
// 找不到对应实现时的兜底,避免直接 404 看不出原因
onCreateRoute: (method: string, descriptor: any[]) => {
console.log(`${method.toUpperCase()} ${descriptor[0]}`);
},
});
connect(app);
app.listen(3001, () => console.log('mock server on http://localhost:3001'));
}
bootstrap();
// src/mock/controllers/index.ts
import type { Request, Response } from 'express';
import { faker } from '@faker-js/faker/locale/zh_CN';
// 函数名必须和 openapi.yaml 里的 operationId 完全一致
export function getUserList(req: Request, res: Response) {
const { page = 1, pageSize = 10 } = req.query;
res.json({
code: 0,
data: {
total: 128,
list: Array.from({ length: Number(pageSize) }, () => ({
id: faker.string.uuid(),
name: faker.person.fullName(),
dept: faker.commerce.department(),
})),
},
});
}
operationId 对不上是这套方案最常见的问题,表现是路由静默不注册、请求 404。swagger-routes-express 提供了 summarise() 用来核对:
import { summarise } from 'swagger-routes-express';
console.log(summarise(apiDefinition, api));
// 会列出 missing(文档有实现没有)和 extra(实现有文档没有)
中间件顺序有讲究
app.use 是按注册顺序执行的,几个位置不能随便调:
cors()要在路由之前,否则预检请求OPTIONS到不了它那里。morgan越早越好,这样连被 CORS 拦掉的请求也能记上一笔。compression()必须在实际返回响应的中间件之前注册,因为它是通过劫持res.write/res.end工作的。express.json()要在需要读req.body的路由之前,漏了会导致req.body是undefined。
mock 服务和前端怎么接
两种接法。一是让前端的 baseURL 直接指向 mock 服务:
const api = axios.create({
baseURL: import.meta.env.VITE_USE_MOCK ? 'http://localhost:3001' : '/api',
});
二是走 Vite 的代理,前端代码完全不用改:
// vite.config.ts
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:3001',
changeOrigin: true,
rewrite: p => p.replace(/^\/api/, ''),
},
},
},
});
第二种更好,因为它保证了 mock 和真实环境下前端代码一模一样,不会出现「mock 能跑真实环境跨域」这类只在切换时才暴露的问题。用代理的话 cors 其实也可以不装——请求是 Vite dev server 转发的,不经过浏览器的同源检查。
更轻的替代方案
如果没有现成的 OpenAPI 文档,这套组合就有点重了。轻量方案:
vite-plugin-mock:直接跑在 Vite dev server 里,不用起独立进程,改 mock 文件热更新。msw:在 Service Worker / Node 层拦截请求,同一套 handler 既能给开发用也能给单测用,这是它最大的优势。json-server:一个 JSON 文件直接变成带增删改查的 REST 服务,做原型最快。
有 OpenAPI 文档时上面那套仍然值得——文档即路由,接口变更不会出现 mock 和真实接口不一致的漂移。

还没有留言,来发表第一条吧~