前后端分离开发时,后端接口还没写好,前端只能干等吗?今天我们用 OpenCode 写一个轻量级 API Mock 服务器——读取 JSON 配置文件,模拟真实的 REST API 响应,前端即刻开工。
技术栈:Node.js 内置模块,零依赖,最终代码约 80 行。支持 GET/POST/PUT/DELETE 方法、动态路由参数、CORS 跨域、可配置延迟模拟。
npm install -g @opencode/cliopencode 进入交互界面我对 OpenCode 说了第一句话:
> 帮我用 Node.js 写一个 API Mock 服务器,读取 mock.json 配置文件,根据请求的 method 和 path 返回对应的 JSON 响应。只用 Node.js 内置模块,不要安装第三方依赖。
OpenCode 分析需求后,立刻创建了 mock-server.js,核心逻辑是:用 http.createServer 创建服务,读取 mock.json 中的路由配置数组,遍历匹配请求的 method 和 path,匹配成功则返回对应的 JSON 响应。同时生成了一个示例 mock.json:
[
{ "method": "GET", "path": "/api/users", "response": { "data": [{ "id": 1, "name": "张三" }] } },
{ "method": "POST", "path": "/api/users", "status": 201, "response": { "msg": "创建成功" } }
]
> 要点:告诉 OpenCode 明确的技术约束("只用内置模块")可以避免它引入不必要的依赖。
我对 OpenCode 补充需求:
> 加上动态路由参数支持,比如 /api/users/:id 能匹配 /api/users/1、/api/users/99 等任意路径,匹配到的参数名和值存到 params 对象。
OpenCode 修改了路由匹配逻辑,新增 matchPath 函数:用正则表达式将 :param 占位符替换为捕获组 ([^/]+),提取出参数名和对应的值。
function matchPath(pattern, pathname) {
const re = new RegExp('^' + pattern.replace(/:\w+/g, '([^/]+)') + '$');
const m = pathname.match(re);
if (!m) return null;
const keys = (pattern.match(/:\w+/g) || []).map(k => k.slice(1));
const params = {};
keys.forEach((k, i) => params[k] = m[i + 1]);
return params;
}
> 要点:逐层叠加需求,每次只让 OpenCode 改一件事,比一次性提一大堆要求更稳妥,生成的代码质量也更高。
我接着说:
> 加上 CORS 跨域头支持,正确处理 OPTIONS 预检请求。POST/PUT 等请求需要解析请求体里的 JSON 数据。
OpenCode 在响应头中统一加了 Access-Control-Allow-Origin 等 CORS 字段,对 OPTIONS 请求直接返回 204。同时写了一个 bodyParser 函数,用 Promise 异步收集请求体数据并解析为 JSON。
> 要点:像 CORS 这类常见需求,OpenCode 凭借训练数据中的大量代码模式,能一次性写对,无需手写模板代码。
我提了最后一个需求:
> 加上命令行参数:-p 指定端口、-c 指定配置文件、-d 指定全局延迟(模拟慢网络)、-h 显示帮助。启动时打印已注册路由列表,请求时打印访问日志。
OpenCode 补齐了参数解析、帮助信息、路由列表打印和请求日志功能。整个过程我没有写一行代码,全部通过自然语言完成。
#!/usr/bin/env node
const http = require('http');
const fs = require('fs');
const url = require('url');
function usage() {
console.log('mock-server - 轻量 API Mock 服务器');
console.log('用法: node mock-server.js [-p port] [-c config] [-d delay]');
console.log(' -p 端口号 (默认 3000)');
console.log(' -c 配置文件路径 (默认 mock.json)');
console.log(' -d 全局响应延迟/ms (默认 0)');
console.log(' -h 显示帮助');
}
const args = process.argv.slice(2);
if (args.includes('-h') || args.includes('--help')) { usage(); process.exit(0); }
const opts = {
config: args.includes('-c') ? args[args.indexOf('-c') + 1] : 'mock.json',
port: args.includes('-p') ? parseInt(args[args.indexOf('-p') + 1]) : 3000,
delay: args.includes('-d') ? parseInt(args[args.indexOf('-d') + 1]) : 0
};
try {
var routes = JSON.parse(fs.readFileSync(opts.config, 'utf-8'));
} catch (e) {
console.error('配置文件读取失败:', e.message);
process.exit(1);
}
function bodyParser(req) {
return new Promise(resolve => {
let data = '';
req.on('data', c => data += c);
req.on('end', () => {
try { resolve(data ? JSON.parse(data) : null); }
catch { resolve(data); }
});
});
}
function matchPath(pattern, pathname) {
const re = new RegExp('^' + pattern.replace(/:\w+/g, '([^/]+)') + '$');
const m = pathname.match(re);
if (!m) return null;
const keys = (pattern.match(/:\w+/g) || []).map(k => k.slice(1));
const params = {};
keys.forEach((k, i) => params[k] = m[i + 1]);
return params;
}
const server = http.createServer(async (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,PATCH,OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
res.setHeader('Content-Type', 'application/json');
if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; }
const { pathname } = url.parse(req.url);
for (const route of routes) {
if (route.method !== req.method) continue;
const params = matchPath(route.path, pathname);
if (!params) continue;
const body = await bodyParser(req);
const delay = route.delay !== undefined ? route.delay : opts.delay;
console.log(`[${new Date().toISOString()}] ${req.method} ${pathname} -> ${route.status || 200}`);
setTimeout(() => {
res.writeHead(route.status || 200);
res.end(JSON.stringify(route.response));
}, delay);
return;
}
res.writeHead(404);
res.end(JSON.stringify({ error: 'Not found', path: pathname }));
});
server.listen(opts.port, () => {
console.log(`Mock Server: http://localhost:${opts.port}`);
console.log(`Config: ${opts.config}`);
console.log('Routes:');
routes.forEach(r => console.log(` ${r.method.padEnd(7)} ${r.path}`));
});
配套的 mock.json 配置文件:
[
{
"method": "GET",
"path": "/api/users",
"response": { "data": [{ "id": 1, "name": "张三" }, { "id": 2, "name": "李四" }] }
},
{
"method": "GET",
"path": "/api/users/:id",
"response": { "data": { "id": 1, "name": "张三", "email": "zhang@example.com" } }
},
{
"method": "POST",
"path": "/api/users",
"status": 201,
"response": { "msg": "创建成功" }
},
{
"method": "PUT",
"path": "/api/users/:id",
"response": { "msg": "更新成功" }
},
{
"method": "DELETE",
"path": "/api/users/:id",
"response": { "msg": "删除成功" }
}
]
运行起来:
node mock-server.js -p 8080 -d 200
curl http://localhost:8080/api/users
curl -X POST http://localhost:8080/api/users -H "Content-Type: application/json" -d '{"name":"王五"}'
从提需求到完整可用的 Mock 服务器,用时不到 4 分钟。全过程我没有敲一行代码,全靠自然语言描述需求,OpenCode 完成了:
OpenCode 的核心价值在于把"想法"直接变成"可运行代码"。省去的不只是打字时间,更是查文档、写样板代码、处理边界情况的脑力开销。对于这类 100 行以内的小工具,用 OpenCode 开发的效率至少是手写的 5 倍——而且全程只说人话就够了。