json-server 前端 Mock 实战:从零搭建 RESTful 模拟接口

发布时间:2026/10/17 16:28:07
json-server 前端 Mock 实战:从零搭建 RESTful 模拟接口
1. 为什么前端开发离不开一个靠谱的 Mock 方案做过前端的人大概都有过这种体验页面骨架搭好了样式调完了接口文档也拿到了结果后端那边告诉你“接口还在联调明天给你”。于是你只能对着空荡荡的列表发呆或者手动在代码里写一堆假数据等真接口来了再一行行删掉。更麻烦的是假数据写死在组件里改一个字段要翻好几个文件团队里每个人写的假数据格式还不一样联调的时候一对接全是坑。json-server这个工具就是冲着这个痛点来的。它做的事情非常朴素你给它一个 JSON 文件它给你一套完整的 RESTful 接口。不用写一行后端代码不用配数据库npx json-server db.json敲下去一个带增删改查的接口服务就跑起来了。对于前端开发者、尤其是需要快速验证页面逻辑和交互流程的场景来说这东西基本属于“用过就回不去”的级别。我最早接触它是在一个后台管理系统的项目里当时后端接口还没影子产品又催着要看效果。用json-server搭了一套模拟接口前端页面该请求请求、该渲染渲染等后端接口好了只需要把请求地址从本地切到真实域名其他代码几乎不用动。这个体验比在代码里硬编码假数据要舒服太多。这篇文章适合几类人看一是刚入行不久、还没怎么接触过前后端分离开发流程的前端新人二是带团队的技术负责人想给团队统一一套 Mock 规范三是做个人项目或者接私活的朋友后端成本能省则省。我会从整体设计思路讲到具体操作细节再到实际踩过的坑尽量把每个环节都讲透。2. json-server 到底解决了什么问题2.1 传统 Mock 方式的几种典型痛点在json-server出现之前前端做 Mock 大概有这么几种路子每一种都有各自的局限。第一种是硬编码假数据。直接在组件里写一个数组或者对象请求的时候不走网络直接返回这个变量。这种方式最简单但问题也最明显数据和逻辑耦合在一起接口字段一变就要改组件代码没法模拟网络延迟和错误状态多个组件共用同一份数据时同步是个麻烦事。第二种是手写一个简易后端服务。用 Node.js 或者 Python 起一个服务手动写几个路由返回假数据。这种方式比硬编码灵活但维护成本高每加一个接口就要写一段路由代码字段改了还要同步改服务端逻辑。对于接口数量多的项目光是维护这个 Mock 服务就够喝一壶的。第三种是用在线 Mock 平台。这类平台通常提供可视化界面能根据接口文档自动生成 Mock 数据。优点是省事缺点是依赖网络、数据在别人服务器上、免费版有各种限制而且接口一多管理起来也很乱。json-server的思路和上面几种都不一样。它不要求你写路由代码也不依赖任何外部平台你只需要维护一个 JSON 文件剩下的交给它。这个 JSON 文件的格式和真实接口返回的数据结构几乎一致改字段就是改 JSON改完保存接口立刻生效连重启都不需要。2.2 json-server 的核心工作机制理解json-server的工作机制对用好它很关键。它的原理其实不复杂读取你指定的 JSON 文件把顶层每个 key 当作一个资源集合然后按照 RESTful 规范自动生成对应的路由。举个例子假设你的db.json长这样{ users: [ { id: 1, name: 张三, role: admin }, { id: 2, name: 李四, role: editor } ], posts: [ { id: 1, title: 第一篇, authorId: 1 } ] }启动服务后你会自动获得这些接口请求方法请求路径作用GET/users获取用户列表GET/users/1获取 id 为 1 的用户POST/users新增用户PUT/users/1全量更新用户PATCH/users/1部分更新用户DELETE/users/1删除用户GET/posts获取文章列表GET/posts?authorId1按条件筛选文章这套路由规则完全遵循 RESTful 约定和真实后端接口的设计方式基本一致。这意味着前端在开发阶段调用的接口形式和上线后调用的真实接口形式可以做到高度统一切换成本极低。另外json-server还支持一些很实用的查询能力比如分页、排序、全文搜索、关联查询等。这些能力在真实项目里经常用到用json-server可以提前把这些交互逻辑跑通。2.3 和其他 Mock 方案的对比为了更直观地说明json-server的定位我整理了一个对比表格对比维度硬编码假数据手写简易后端在线 Mock 平台json-server上手成本极低中等低极低维护成本高高中低接口真实性差好中好网络行为模拟无可定制有限支持延迟数据持久化无取决于实现云端本地文件团队协作差中好好文件可共享离线可用是是否是学习曲线无陡平平从这张表能看出来json-server在“上手成本”和“接口真实性”这两个维度上做到了很好的平衡。它不像在线平台那样依赖网络也不像手写后端那样需要维护路由逻辑同时又能提供接近真实接口的体验。3. 从零搭建一套可用的 Mock 服务3.1 环境准备与安装方式选择json-server是一个基于 Node.js 的命令行工具所以第一步是确保你的机器上装了 Node.js。版本方面建议用 14 以上的 LTS 版本太老的版本可能会有兼容性问题。装好 Node.js 之后npm也就自带了。安装json-server有两种方式各有适用场景。第一种是全局安装npm install -g json-server全局安装的好处是随时随地都能用json-server命令适合经常需要起 Mock 服务的开发者。缺点是版本固定在全局环境里不同项目如果需要不同版本会有点麻烦。第二种是项目内安装npm install json-server --save-dev这种方式把json-server作为项目的开发依赖版本跟着项目走团队里每个人用的版本一致。用的时候通过npx json-server或者配置到package.json的scripts里。我个人更推荐这种方式尤其是团队协作场景能避免“我这里能跑你那里报错”的尴尬。如果你只是想快速试一下不想安装任何东西也可以直接用npxnpx json-server db.jsonnpx会临时下载并执行用完即走适合尝鲜。3.2 数据文件的结构设计数据文件是json-server的核心它的结构设计直接决定了你拿到的接口好不好用。这里有几个原则值得注意。第一顶层 key 用复数形式。比如users、posts、comments这样生成的路由/users、/posts符合 RESTful 习惯。虽然单数也能用但和真实接口对接时容易产生认知偏差。第二每个资源都要有id字段。json-server依赖id来定位单条记录没有id的话GET /users/1这类接口就没法正常工作。id可以是数字也可以是字符串但同一个集合里最好保持一致。第三关联关系用外键字段表达。比如文章属于某个用户就在文章对象里放一个authorId字段指向用户的id。json-server支持通过查询参数做关联查询比如GET /posts?authorId1就能拿到某个用户的所有文章。第四数据量要适中。太少看不出分页和筛选效果太多文件会很大、加载慢。一般每个集合放 20 到 50 条数据比较合适既能演示分页又不至于让文件臃肿。一个比较完整的数据文件示例{ users: [ { id: 1, name: 张三, email: zhangsanexample.com, role: admin }, { id: 2, name: 李四, email: lisiexample.com, role: editor } ], posts: [ { id: 1, title: 前端工程化实践, content: ..., authorId: 1, createdAt: 2024-01-15 }, { id: 2, title: 组件设计思路, content: ..., authorId: 2, createdAt: 2024-01-16 } ], comments: [ { id: 1, postId: 1, userId: 2, content: 写得好 } ] }这个结构覆盖了一对多和多对多的典型场景基本能满足大部分中小型项目的 Mock 需求。3.3 启动服务与常用参数配置数据文件准备好之后启动服务只需要一行命令json-server --watch db.json默认情况下服务跑在http://localhost:3000接口路径就是数据文件的顶层 key。比如http://localhost:3000/users就能拿到用户列表。但实际项目里默认配置往往不够用下面这几个参数是我用得最多的--port指定端口。前端项目通常跑在 3000 或 8080如果和json-server冲突了就换一个json-server --watch db.json --port 3001--host指定主机。默认只监听本地如果想让同一局域网的其他设备比如手机也能访问需要指定0.0.0.0json-server --watch db.json --host 0.0.0.0--delay模拟网络延迟。这个参数特别实用可以模拟真实网络环境下的加载状态方便调试 loading 效果json-server --watch db.json --delay 1000上面这条命令会让所有接口延迟 1 秒返回。你也可以针对特定请求设置延迟不过那需要更复杂的配置后面会讲到。--routes自定义路由。如果你的前端请求路径和json-server默认生成的不一致可以通过一个路由配置文件做映射json-server --watch db.json --routes routes.jsonroutes.json的内容大概长这样{ /api/*: /$1, /v2/users: /users }这样/api/users就会被映射到/users方便模拟带前缀的真实接口。--middlewares加载中间件。这是json-server最强大的扩展能力后面会专门讲。把这些参数组合起来一个比较完整的启动命令是json-server --watch db.json --port 3001 --host 0.0.0.0 --delay 500 --routes routes.json我一般会把这个命令写进package.json的scripts里比如{ scripts: { mock: json-server --watch mock/db.json --port 3001 --delay 300 } }这样团队里任何人拉下代码跑一个npm run mock就能起服务不用记那一长串参数。4. 进阶玩法让 Mock 服务更贴近真实后端4.1 用中间件处理复杂业务逻辑json-server默认只提供标准的 CRUD 接口但真实项目里往往有一些非标准的需求比如登录鉴权、数据格式包装、自定义错误码等。这些需求可以通过中间件来实现。中间件本质上就是一个 Express 的中间件函数json-server底层用的就是 Express所以你可以用 Express 的写法来扩展它。创建一个middleware.jsmodule.exports (req, res, next) { // 模拟登录接口 if (req.path /login req.method POST) { const { username, password } req.body; if (username admin password 123456) { return res.json({ code: 0, data: { token: mock-token-xxx, username: admin }, message: 登录成功 }); } return res.status(401).json({ code: 401, message: 用户名或密码错误 }); } next(); };然后在启动时加载json-server --watch db.json --middlewares middleware.js这个中间件拦截了/login请求返回了一个模拟的登录响应。真实项目里你可以用同样的方式处理权限校验、数据脱敏、请求日志等逻辑。需要注意的是中间件的执行顺序很重要。json-server会先执行你传入的中间件如果中间件里没有调用next()请求就不会继续走到默认的 CRUD 处理逻辑。所以如果你只想在特定路径上做处理记得在其他情况下调用next()。4.2 自定义响应格式与状态码真实后端的响应格式往往不是裸数据而是包了一层比如{ code: 0, data: { ... }, message: success }json-server默认返回的是裸数据要模拟这种包装格式有两种做法。一种是在中间件里统一处理响应。但这种方式需要拦截res.json稍微有点绕。另一种更简单的做法是在前端请求层做适配把json-server的裸数据包装成项目需要的格式。我一般倾向于后者因为 Mock 层的职责就是提供数据格式转换放在前端请求封装里更合理而且切换真实接口时也不用改。不过如果你确实需要在 Mock 层做包装可以这样写中间件module.exports (req, res, next) { const originalJson res.json.bind(res); res.json (body) { // 对 GET 请求的列表数据做包装 if (req.method GET Array.isArray(body)) { return originalJson({ code: 0, data: { list: body, total: body.length }, message: success }); } return originalJson(body); }; next(); };这段代码重写了res.json方法对数组类型的响应做了包装。实际用的时候要根据项目约定的格式来调整。4.3 分页、排序与筛选的实战用法json-server内置了一套查询参数能实现大部分列表页需要的功能。这些参数直接拼在 URL 上用起来非常方便。分页用_page和_limitGET /posts?_page1_limit10响应头里会带上X-Total-Count表示总条数前端可以据此计算总页数。排序用_sort和_orderGET /posts?_sortcreatedAt_orderdesc_order可以是asc或desc。范围查询用_gte和_lteGET /posts?createdAt_gte2024-01-01createdAt_lte2024-01-31模糊搜索用qGET /posts?q前端这会在所有字段里搜索包含“前端”的记录。关联查询用外键字段GET /posts?authorId1多条件组合也没问题GET /posts?_page1_limit5_sortcreatedAt_orderdescauthorId1这些参数组合起来基本能覆盖列表页的常见交互。我建议在项目初期就把这些查询逻辑跑通这样等真实接口来了前端代码几乎不用改。有一个细节需要注意json-server的模糊搜索q是大小写不敏感的而且会匹配所有字段。如果你的数据里有不需要搜索的字段可能会返回意料之外的结果。这种情况可以通过自定义中间件来精确控制搜索逻辑。5. 实际项目中的踩坑记录与排查技巧5.1 跨域问题的处理方式前端项目跑在localhost:8080json-server跑在localhost:3000浏览器会拦截跨域请求。这是最常见的问题之一。json-server默认是开启了 CORS 的理论上不会有跨域问题。但如果你用了自定义中间件或者在中间件里修改了响应头可能会把 CORS 头覆盖掉。遇到跨域报错时先检查中间件里有没有动res.header。如果确实需要手动配置 CORS可以在中间件里加上module.exports (req, res, next) { res.header(Access-Control-Allow-Origin, *); res.header(Access-Control-Allow-Headers, Content-Type, Authorization); res.header(Access-Control-Allow-Methods, GET, POST, PUT, PATCH, DELETE, OPTIONS); if (req.method OPTIONS) { return res.sendStatus(200); } next(); };另一种更省事的做法是在前端配置代理。比如用 Vite 的话在vite.config.js里配export default { server: { proxy: { /api: { target: http://localhost:3000, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) } } } }这样前端请求/api/users实际会被代理到http://localhost:3000/users浏览器看来是同源请求不会有跨域问题。这种方式更接近生产环境的部署方式我个人更推荐。5.2 数据写入不生效的排查思路json-server的 POST、PUT、PATCH、DELETE 操作会直接修改db.json文件。但有时候你会发现接口返回成功了文件却没变或者重启服务后数据又回去了。这种情况通常有几个原因。一是启动时没有加--watch参数json-server只在内存里操作不会写回文件。加上--watch就能解决。二是文件权限问题。如果db.json是只读的或者所在目录没有写权限写入就会失败。检查一下文件权限确保当前用户有写权限。三是用了某些编辑器或工具锁定了文件。比如有些编辑器在文件被外部修改时会弹窗提示如果没确认写入可能被阻塞。关掉编辑器或者换个方式编辑就能解决。还有一个容易忽略的点如果你在中间件里拦截了 POST 请求但没有调用next()请求就不会走到json-server的默认处理逻辑数据自然不会写入。排查的时候可以先去掉中间件看看问题是否还在。5.3 常见问题速查表问题现象可能原因解决方法启动报错command not found没有安装或没加到 PATH用npx json-server或检查全局安装路径接口 404路径拼写错误或资源名不对检查数据文件顶层 key 和请求路径是否一致跨域报错中间件覆盖了 CORS 头检查中间件或在前端配代理数据写入不生效没加--watch或文件权限问题加--watch检查文件写权限分页总数不对没读X-Total-Count响应头在请求拦截器里读取该响应头关联查询返回空外键字段名或值不匹配检查数据里的外键字段和查询参数是否对应中文搜索无结果编码问题确保请求 URL 做了 encodeURIComponent服务启动后端口被占用端口冲突用--port换一个端口5.4 几个提升效率的实操心得第一个心得是把 Mock 数据纳入版本管理。db.json和相关的中间件、路由配置文件都提交到 Git 仓库团队里每个人拉下来就能用同一套数据。这样联调的时候不会出现“你那边有这条数据我这边没有”的情况。第二个心得是给数据文件做备份。因为json-server的写操作会直接改文件如果不小心发了一个 DELETE 请求把数据删了文件里的数据就真没了。我一般会保留一个db.backup.json需要重置的时候直接覆盖回去。也可以在package.json里加一个重置脚本{ scripts: { mock:reset: cp mock/db.backup.json mock/db.json } }第三个心得是用环境变量区分 Mock 和真实接口。在前端请求层封装一个基础 URL开发环境指向json-server测试和生产环境指向真实后端。这样切换的时候只需要改一个配置不用满项目找请求地址。const BASE_URL import.meta.env.DEV ? http://localhost:3000 : https://api.example.com;第四个心得是不要过度依赖 Mock 数据。json-server适合在接口未就绪时快速搭建开发环境但它毕竟不是真实后端没法模拟复杂的业务逻辑、并发场景和异常情况。接口一旦就绪尽早切换到真实接口联调避免在 Mock 环境里待太久导致后期对接出问题。6. 把 json-server 用出花来的几个场景6.1 个人项目快速原型验证做个人项目或者接私活的时候后端往往是最耗时的部分。用json-server先把前端跑起来把页面交互和业务流程验证一遍等确认没问题了再决定后端怎么做。有时候验证下来发现需求本身就不成立省下的后端开发时间就是纯赚。我自己的做法是先用json-server搭一个最小可用的数据层把核心流程跑通。如果项目继续推进再逐步把json-server替换成真实后端。因为接口形式一致替换过程基本就是改一下基础 URL。6.2 团队协作中的接口契约先行在团队协作里前后端经常因为接口字段对不齐而扯皮。用json-server可以做到“接口契约先行”前后端一起把接口字段定下来前端用json-server按这个契约搭 Mock后端按这个契约实现真实接口。等两边都完成了一对接就能通省去了大量沟通成本。具体操作上可以把db.json当作接口契约的可执行版本。后端实现接口时直接参考db.json里的数据结构字段名、类型、嵌套关系一目了然。这比看文档要直观得多。6.3 前端自动化测试的数据支撑做前端自动化测试的时候经常需要稳定的测试数据。json-server可以作为测试环境的 Mock 服务每次测试前重置数据文件保证每次测试的数据状态一致。配合测试框架可以在测试启动前用脚本重置db.json测试结束后再恢复。这样测试用例之间不会互相干扰测试结果也更可靠。6.4 教学演示与新人练手带新人的时候json-server是个很好的教学工具。新人可以用它快速理解 RESTful 接口的设计规范、HTTP 方法的使用场景、前后端数据交互的流程而不需要先花大量时间学后端框架。我一般会让新人先用json-server做一个完整的增删改查页面把请求发送、响应处理、状态更新这一套流程走一遍。等他们熟悉了再引入真实后端学习曲线会平缓很多。7. 一些值得注意的边界与替代方案json-server虽好但也不是万能的。它的定位很明确轻量级的本地 Mock 服务。如果你的需求超出了这个范围可能需要考虑其他方案。比如你需要模拟 WebSocket 实时通信json-server就不支持。你需要模拟文件上传和下载它也只能做很有限的模拟。你需要模拟复杂的鉴权流程比如 OAuth虽然可以用中间件实现但会比较繁琐。另外json-server的性能有限不适合做高并发的压力测试。它的数据存储基于文件数据量大了之后读写效率会下降。如果你的 Mock 数据超过几千条可能需要考虑用更专业的 Mock 方案。还有一些场景下用 MSWMock Service Worker这类在浏览器层面拦截请求的方案可能更合适。它不需要起额外的服务Mock 逻辑和前端代码在一起切换也更方便。不过 MSW 的学习成本比json-server高一些配置也更复杂。选择哪种方案取决于你的具体需求和团队情况。对于大多数中小型项目的前端开发阶段json-server的性价比是最高的。它足够简单足够好用该有的功能都有不该有的复杂度一点没多。我在实际使用中的体会是json-server最大的价值不在于它提供了多少功能而在于它把 Mock 这件事的门槛降到了几乎为零。你不需要学任何新概念不需要配任何复杂的环境一个 JSON 文件加一行命令就能得到一个可用的接口服务。这种“刚刚好”的设计哲学才是它真正让人离不开的原因。最后分享一个小技巧如果你经常需要切换不同的 Mock 数据集可以准备多个 JSON 文件比如db.empty.json、db.normal.json、db.large.json分别对应空数据、常规数据和大数据量场景。启动时通过参数指定用哪个文件就能快速切换测试场景。这个做法在调试分页、空状态、加载状态这些边界情况时特别有用。