◆

Node.js Mock 服务依赖包安装清单

2023-02-02 01:370

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 和真实接口不一致的漂移。

💬留言板0

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

PAGE LOAD / LIVENode.js Mock 服务依赖包安装清单加载中