后端接口一直还没好?用 Node 搭了个本地Mock服务不耽误前端开发进度
前后端并行开发,前端页面写好了接口还没出,只能干等。或者在代码里写死假数据,联调时又要逐个删掉,容易漏。
做了个本地 Mock 服务:用配置文件描述接口,起一个真的 HTTP 服务返回假数据,前端代码按正常方式请求。联调时只要把接口地址换掉,业务代码不用动。
一、路由定义和匹配
const ROUTES = [
{ method: 'GET', path: '/api/users', handler: () => ({ code: 0, data: makeUsers(10) }) },
{ method: 'GET', path: '/api/users/:id', handler: (p) => ({ code: 0, data: makeUsers(1)[0] }) },
{ method: 'POST', path: '/api/login', handler: (p, body) => ({ code: 0, token: 'mock-token' }) },
];
function matchRoute(method, url) {
const pathname = url.split('?')[0];
for (const r of ROUTES) {
if (r.method !== method) continue;
const rp = r.path.split('/');
const up = pathname.split('/');
if (rp.length !== up.length) continue;
const params = {};
let ok = true;
for (let i = 0; i < rp.length; i++) {
if (rp[i].startsWith(':')) params[rp[i].slice(1)] = up[i];
else if (rp[i] !== up[i]) { ok = false; break; }
}
if (ok) return { route: r, params };
}
return null;
}
按段匹配,:id 这种动态段收集到 params。先比段数再逐段比,段数不同直接跳过,省去不必要的循环。
二、处理请求和 CORS
function createServer({ delay = 200 } = {}) {
return http.createServer((req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
if (req.method === 'OPTIONS') {
res.statusCode = 204;
return res.end();
}
const hit = matchRoute(req.method, req.url);
if (!hit) {
res.statusCode = 404;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
return res.end(JSON.stringify({ code: 404, message: 'no mock route' }));
}
readBody(req).then((body) => {
setTimeout(() => {
const result = hit.route.handler(hit.params, body);
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify(result));
}, delay);
});
});
}
浏览器发非简单请求时先发一次 OPTIONS 预检,不处理的话正式请求根本发不出去。预检直接返回 204,不带响应体。
delay 模拟网络耗时,不然本地太快,加载状态和竞态问题在联调时才暴露——那时候改起来就晚了。
三、读取请求体
function readBody(req) {
return new Promise((resolve) => {
if (req.method === 'GET') return resolve(null);
let raw = '';
req.on('data', (chunk) => {
raw += chunk;
if (raw.length > 1e6) req.destroy();
});
req.on('end', () => {
try {
resolve(raw ? JSON.parse(raw) : null);
} catch {
resolve(null);
}
});
});
}
请求体是流式的,要自己收完再解析。加了大小上限防止超大 body 把内存打满,JSON.parse 失败时返回 null 而不是让进程崩掉。
四、踩过的坑
第一个坑是预检请求没处理。 前面说的,带 Authorization 头或者用 application/json 的请求都会触发 OPTIONS,不返回正确头,浏览器直接拦掉。
第二个坑是没模拟延迟。 本地毫秒级返回,前端没写 loading 或者没处理竞态,联调到真实网络就出问题。加了默认延迟之后这些问题在开发阶段就能发现。
第三个坑是假数据太规整。 每条记录的字段都完整、数组长度固定,前端没处理缺省值,真实数据一来就报错。所以 Mock 里要故意造一些缺字段、空数组、超长文本的数据。
第四个坑是请求体没解析。 用了框架的话这步是自动的,自己写就会漏。
第五个坑是 Mock 数据进了生产。 配置文件里的假数据如果和业务代码混在一起,打包时会一起进去。Mock 应该只在开发环境启用,通过环境变量控制,并且路径跟真实接口分开配置。
第六个坑是改配置要重启。 早期版本改一次路由重启一次服务,很烦。后来加了监听配置文件变化自动重载,开发体验好很多。
五、最后总结
Mock 服务的意义不只是"造数据",更重要的是让前端在早期就能按真实的网络环境开发——有延迟、有错误码、有缺字段。这样联调时改的只是接口地址,不是业务逻辑。
后来加了按场景切换响应。同一个接口有时候要返回成功,有时候要返回失败来测错误处理,支持给路由配置多个响应,通过查询参数或者环境变量切换。
另外支持动态数据。假数据里的 id、时间戳每次生成新的,避免前端把假数据当真的缓存住。
还有个功能是记录请求日志。收到的请求打到控制台,前端说接口没返回时先看 mock 有没有收到,定位快。
最后是配置文件分离。路由和假数据放单独文件,主程序只负责起服务,改数据不用动代码。
还有个功能是状态码模拟。除了正常返回,也支持配置 401、500 这类错误码,用来测前端的错误处理。另外支持按概率失败:每次请求有一定概率出错,能测出前端在偶发错误下的表现,比固定失败更接近真实。最后是约定:mock 只在开发环境启用,构建时不能打进去。
另外支持自定义响应头,有些前端逻辑依赖特定的头部字段,mock 里得能配上。还有个细节是返回体支持直接读文件内容,假数据太多时写在配置文件里更清楚。
还有个细节是支持热重载,改了配置文件不用重启服务,开发时省很多事。另外支持按环境切换 mock 开关,避免忘记关闭导致上线后还在用假数据。
- 点赞
- 收藏
- 关注作者
评论(0)