
做前端开发这几年我最怕听到的一句话不是“这个需求下周一上线”而是“后端接口下周才给你先看文档把页面写了”。文档里往往只有十几个字段名返回结构写得模棱两可等后端真正联调时才发现字段大小写对不上、嵌套层级差了一层、分页参数完全不是约定那一套。后来我接触到 json-server才发现原来一个 JSON 文件加一条命令就能在十分钟内拥有一套看起来像模像样的后端接口。它既能支撑前端页面开发又能拿来给同事做 demo甚至还能在小型活动页里临时充当数据服务。这篇文章我就围绕 json-server 这个零代码后端模拟神器把从安装、路由规则到进阶用法的完整经验整理出来方便你在下一个前后端并行开发的项目里直接照抄。1. 后端接口没就绪时前端开发为什么要备一套 mock 武器1.1 前后端并行开发里时间都浪费在哪些地方前后端分离之后理论上两边可以按接口文档并行推进但实际项目里“接口文档先行”往往只是理想状态。需求评审刚结束后端要排期做表结构设计前端却已经接到了“先搭页面框架”的任务。你总不能对着空白页面干等一周。常见做法是自己写一套本地 mock比如在 webpack 或者 vite 的 dev server 里配几个中间件又或者用工具拦截请求返回固定 JSON。这些方案能跑但有一个通病mock 逻辑分散在前端工程里只对自己本地生效换台电脑或者换个人来协作mock 代码能不能跑起来都成问题。json-server 不一样。它把后端模拟这事从“前端工程内部”剥离出来变成一个独立运行的服务进程。你只需要维护一个 JSON 数据文件它就能生成一套完整的 RESTful 接口包括列表查询、单条读取、新增、修改、删除这些基本操作。前端代码里只关心 axios 请求的 URL完全不需要知道数据是从 json-server 来的还是从真实后端来的。联调切换的时候只要把环境变量里的 baseURL 改一下页面代码零改动就能接上真实服务。1.2 json-server 的定位和边界它能做什么不能做什么很多人第一次用 json-server 会误以为它是数据库或者是一个低代码后端平台其实它的定位很简单基于一个 JSON 文件快速生成 REST API 的服务。它适合做这几类事前端页面开发时提供稳定的假接口给 UI 设计稿配一份可点击的演示环境写单元测试或集成测试时作为可控的依赖服务本地开发小程序或 App 时充当临时后端给演示项目提供一个不依赖真实环境的后端底座它的边界也很清晰没有用户体系没有权限控制没有事务保障数据持久化就是把整个 JSON 文件重写一遍。并发量一高就会出问题所以它只适合开发调试不适合做生产服务。理解这个边界很重要因为很多人踩坑就是因为在错误的环境里用了它。我在项目里见过一个反面案例某个内部管理系统的报表导出功能为了快速上线直接把 json-server 部署到了内网服务器上当真实接口用。刚开始数据量小没事后来有人开始往里写业务数据一旦两个人同时提交文件就会被覆盖。最后运维排查了半天发现数据全丢在了一个 json 文件里。所以使用之前心里要有数它是模拟工具不是生产数据库。2. 一条命令跑起来的接口服务安装、数据结构与基础路由规则2.1 安装方式和第一条启动命令json-server 是一个 Node.js 工具用 npm 全局安装或者用 npx 直接执行都行。个人推荐在项目里作为 devDependency 安装锁版本避免不同机器上行为不一致。npm install json-server --save-dev然后准备一个数据文件习惯上叫 db.json放在项目根目录或者 mock 目录下。最小示例{ posts: [ { id: 1, title: json-server 入门, author: 张三 }, { id: 2, title: 零代码后端模拟, author: 李四 } ], comments: [ { id: 1, postId: 1, body: 写得好 }, { id: 2, postId: 1, body: 收藏了 } ] }启动命令npx json-server db.json默认监听 3000 端口启动后终端会打印出所有可用的路由地址。浏览器打开http://localhost:3000/posts就能看到 posts 列表打开根路径http://localhost:3000/会进入一个可视化操作界面可以在页面上直接测接口。这里注意一个细节json-server 默认会监听 db.json 文件的变化也就是“watch 模式”。你改了文件内容服务会自动重载数据部分场景下甚至不用重启。但文件格式一旦写错比如多加了一个逗号服务会直接崩溃或者报错需要重启。开发时改完数据最好看一眼终端日志。2.2 db.json 的字段设计主键、外键和嵌套结构db.json 的结构决定了生成的接口形态。最外层是一个 JSON 对象每个 key 对应一个资源名value 是数组数组里每个元素就是一条记录。资源名会被直接拼成路由路径比如posts对应/posts。每条记录必须有唯一的主键默认字段名是id可以是数字也可以是字符串。如果 POST 提交的数据里没有 idjson-server 会自动生成一个随机递增的数字 id。这里建议前端小伙伴注意如果你们的前端代码依赖 id 是数字类型就在 Mock 数据里显式用数字如果后端的 id 是字母数字混合字符串也先把类型定好避免前后联调时类型不一致。资源之间可以表达一对多关系最朴素的方式就是用外键字段比如示例里的postId指向posts.id。json-server 支持两种关系查询方式_embed和_expand我后面会专门展开讲。嵌套结构也可以写但不太推荐因为嵌套层级一旦加深路由规则会变得不符直觉。比如comments: [{ post: { id: 1 } }]这种写法增删改查都会变得别扭。2.3 基础路由规则RESTful 风格是从文件名自动长出来的不需要写任何路由配置json-server 会根据资源名自动生成一整套 RESTful 路由。这是它“零代码”气质的核心体现。自动生成的路由完整列表如下HTTP 方法路由作用GET/posts获取列表GET/posts/1获取单条POST/posts新增一条PUT/posts/1整体替换单条PATCH/posts/1局部更新单条DELETE/posts/1删除单条这个设计天然对应前端常用的 ajax 库和 fetch API不需要做任何适配。前端代码里写const response await fetch(/posts, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ title: 新文章, author: 王五 }) });数据就会被写入 db.json然后出现在/posts列表里。整个过程不需要写后端逻辑。还有一个容易被忽略的接口GET /posts?_page1_limit2能拿到分页数据响应头里的X-Total-Count会返回总条数。这个在后面的查询语法部分细说。3. 查询语法里那些每天都会碰到的细节过滤、排序、分页与关系字段3.1 过滤、排序与分页的正确打开方式json-server 的查询语法很像后端框架自动生成的列表查询接口熟悉之后会觉得无比顺手。先说最常用的几个参数。按字段精确过滤直接拼 URL queryGET /posts?author张三 GET /posts?id1id2 GET /comments?postId1模糊搜索用_likeGET /posts?title_likejson范围筛选用_gte、_lteGET /posts?views_gte100views_lte500排除某个值用_neGET /posts?author_ne张三排序用_sort和_orderGET /posts?_sortviews_orderdesc GET /posts?_sortauthor,views_orderasc,desc第二个示例是多字段排序第一个字段按作者升序第二个字段按浏览量降序。这个细节很多人第一次用会卡住以为_order只能传一个值其实它可以写成逗号分隔和_sort里的字段一一对应。分页有两种模式。一种是公开的_page和_limitGET /posts?_page2_limit10响应头里会带X-Total-Count告诉你有多少条记录前端可以据此计算总页数。另一种是区间截取用_start和_endGET /posts?_start0_end10也可以配合_limit单独用。区别在于区间截取不返回X-Total-Count响应头里没有总条数。如果前端的分页组件依赖总数做页数展示就用_page方案。3.2 关系字段的嵌套查询和全文检索实际业务里列表页经常要同时展示关联信息。比如文章列表要显示作者的昵称和头像评论列表要显示文章标题。这种需求在真实后端往往要用 join 查询在 json-server 里则通过资源关系自动处理。先交代两个概念。_embed是“把子资源嵌进来”内容方向是父级读取子级_expand是“把父资源展开”内容方向是子级读取父级。GET /posts?_embedcomments返回结果里每篇文章会多出一个comments数组里面是postId等于当前文章 id 的评论。GET /comments?_expandpost返回结果里每条评论会多出一个post对象内容是外键指向的文章。两个方向也可以组合使用GET /posts?_embedcomments_expanduser前提是你的数据文件里有对应的资源和外键字段。如果资源名拼错了它不会报错只是不展开任何字段。这个特性用来模拟联调阶段的详情页数据非常方便不用前端自己拼多个请求。全文检索用q参数它会扫描整个资源里的所有字段进行模糊匹配GET /posts?qjson-server这个功能在写全局搜索框原型时很好用一行配置都不用写直接把搜索关键词拼到 query 里就行。不过要注意q的是全字段检索性能和语义都不如真实后端的全文检索只适合模拟阶段使用。3.3 响应体里那些容易混淆的字段关于 embed 和 expand 的选择我用 json-server 折腾了几个月之后最大的体会是_embed和_expand虽然看起来接近但选错会导致返回数据的结构非常别扭前端解析代码跟着写错。举例说评论列表页通常需要展示“评论内容 所属文章标题”。这时候用/comments?_expandpost返回的每条评论是一个扁平的post字段嵌套在评论对象里前端代码写成comment.post.title就行清晰直观。但如果需求是“文章列表 每篇文章的前三条评论”用/posts?_embedcomments也顺理成章。麻烦的场景是评论区下方还要展示“评论者信息”数据结构变成了评论里嵌文章、文章里嵌评论者很容易出现循环嵌套。实际使用中要克制不要用一个 super 长 URL 把所有关系都嵌进来不然返回 JSON 会非常庞大前端调试起来也头疼。我的习惯是详情页用_embed列表页用_expand。这个偏好不一定是标准答案但能让 mock 数据和真实后端返回结构最接近避免换到真实接口时前端代码大改。4. 从静态数据到业务状态机POST、PUT、PATCH 与自定义路由的玩法4.1 三种写操作的语义差异很多刚接触 json-server 的同学会困惑POST、PUT、PATCH 到底有什么区别在真实后端里这三个方法语义不同在 json-server 里它们对应着不同的数据处理方式。POST新增一条数据。没传 id 时自动生成传了 id 就按传的值存。PUT整体替换。前端必须提交完整的对象尤其是要把 id 一起带上。PATCH局部更新。只要提交需要修改的字段集合就行。DELETE删除指定 id 的数据。实际开发中前端最常用的是 POST 和 PATCH。PUT 用得少因为很多业务场景里只需要改某个字段整体替换容易把其他字段意外清空。你在 json-server 里测试 PUT 时要特别注意它不会自动保留缺失的字段没传的都视为空。这个行为和某些后端不一致联调时容易造成“Mock 时好好的一接真实环境就缺字段”的错觉。POST 提交时的数据结构还有一个细节如果提交的是数组json-server 会批量插入如果提交的是普通对象就只插一条。这个批量插入特性在初始化测试数据时很好用。4.2 用 routes 文件改写 URL让接口路径贴近后端规范真实后端接口路径往往带前缀比如/api/posts或者/v1/users。json-server 默认生成的路径是不带前缀的直接使用会导致前端代码在 Mock 和真实环境之间切换时需要额外改 baseURL。解决办法是创建一份routes.json{ /api/*: /$1, /v1/posts: /posts, /v1/posts/:id: /posts/:id }启动时加参数npx json-server db.json --routes routes.json这样GET /api/posts/1会被改写映射到GET /posts/1。前端代码里的请求地址始终保持真实环境的路径风格Mock 环境下由 json-server 做一次转换。routes 文件里的写法支持通配符和路径参数。我最常用的是第一个/api/*: /$1意思是将/api/后面的部分直接匹配到原始路由。这样整个项目的 URL 前缀统一成/api最贴近真实环境。后面几个具体路径的配置适合做差异化定制比如当某个资源在真实后端不叫这个名或者嵌套关系更深时可以灵活重写。4.3 浏览器端的可视化操作面板启动后打开根路径json-server 内置了一个简单的操作面板。左侧是资源列表右侧展示每条数据的 JSON 内容顶部能切换 GET、POST、PUT、PATCH、DELETE 等操作。这个面板对不会写命令行的同事特别友好产品经理或者设计师想看数据长什么样直接点开就能浏览不需要让他们学 curl 或 Postman。面板底部还有个“新资源”输入框可以直接往 db.json 里加初始数据。我在团队里试过几次大家上手没有门槛。当然真正常用接口的还是习惯用命令行工具或者 Postman但把它作为团队的 mock 数据展示入口比让新人直接读 JSON 文件体验好很多。5. 模拟数据接近生产环境的最后一公里中间件、造数与持久化5.1 用 middleware 模拟网络延迟、登录态和按条件返回这一节是 json-server 从“玩具”走向“工具”的分水岭。默认情况下接口响应是即时的页面开发时看不出加载态和 loading 效果。真实网络的延迟、请求失败、登录态失效这些场景都需要靠中间件模拟。json-server 支持通过-m参数挂载自定义中间件npx json-server db.json -m ./middleware.js中间件文件内容大致如下module.exports function (req, res, next) { // 模拟网络延迟 setTimeout(next, 500); };更复杂的场景可以玩出这些花样模拟登录校验如果请求头里没有Authorization: Bearer xxx直接返回 401。模拟随机失败根据概率返回 500让前端处理错误分支。模拟接口限流某个接口连续点击 N 次后返回 429。模拟业务错误根据请求体内容返回{ code: 10001, message: 库存不足 }。这些能力让前端能在本地就把异常跑通不需要等真实后端配合。尤其是权限校验真实环境里调接口要带 tokenMock 阶段如果完全不校验前端代码一旦把 token 逻辑写成“先判断有无再请求”到联调阶段就会漏掉 token 的边界处理。用中间件提前模拟比联调时再发现问题省心得多。5.2 批量造数用脚本生成 db.json 而不是手写手写 5 条测试数据没问题但要写 200 条分页数据手写既费时又容易重复。推荐做法是写一个 Node 脚本用循环生成数据然后写入 db.json。下面是一个实际用过的造数脚本示例生成 150 篇文章和 300 条评论const fs require(fs); const posts []; const comments []; const authors [张三, 李四, 王五, 赵六]; for (let i 1; i 150; i) { posts.push({ id: i, title: 文章标题 ${i}, author: authors[i % authors.length], views: Math.floor(Math.random() * 1000), createdAt: new Date(Date.now() - i * 86400000).toISOString() }); } let commentId 1; for (let i 1; i 150; i) { const count Math.floor(Math.random() * 3) 1; for (let j 0; j count; j) { comments.push({ id: commentId, postId: i, body: 评论内容 ${commentId} }); } } const db { posts, comments }; fs.writeFileSync(./db.json, JSON.stringify(db, null, 2));生成之后直接启动 json-server接口数据量就足够前端调试分页和排序了。脚本本身建议放在 mock 目录下和 db.json 放一起方便后来的人重新生成。还有一个小技巧如果项目本身是前端工程可以把造数脚本接到package.json的 scripts 里比如mock:generate: node mock/generate.js。这样团队成员一键就能重新生成数据不用互相拷贝 db.json。5.3 数据持久化的读写时机和隐患json-server 收到写操作POST、PUT、PATCH、DELETE后会更新内存中的数据然后同步把整个 db.json 重写一遍。这意味着每次写操作都是全量写文件数据量大起来会有明显卡顿。我测试过一个数据规模约 2 万条记录的 db.json文件体量大概 5MB。每执行一次 POST 或 DELETE服务端大约要花几百毫秒重写文件。如果前端连续发多个写请求表现可能是 ajax 排队接口变慢。在纯 Mock 场景下可以接受但如果你的演示项目需要频繁写数据就要考虑减少数据量或者把无关历史数据拆分到另一个 JSON 文件。json-server 还支持--static参数指定静态资源目录也能通过--delay参数给所有响应加统一延迟。这些参数组合起来可以比较接近地模拟一个状态比较真实的慢接口服务。6. 我在真实项目里踩过的坑以及最终沉淀下来的工作流6.1 并发写入导致数据丢失这个坑我前面提过这里详细展开。有一次我们在做内部运营后台的前端原型多人同时使用同一个 json-server数据文件放在共享目录里。某个下午运营同事反馈“文章标题改了保存后过一会儿又变回旧值”。排查后发现原因是两个同事同时打开页面各自编辑不同文章先后提交后端。每次提交都会全量重写 db.json 文件后一次提交覆盖前一次导致数据互相覆盖。这类问题不是代码 bug而是 json-server 的并发模型天然不适合多人写。解决思路有三种只让一个人负责维护 db.json其他人通过接口读写避免手工改文件。演示场景下把写操作禁止掉只保留查询权限可以在中间件里拦截写方法。多人协作时共用一个 mock 服务但约定好每个人只操作自己负责的资源前缀减少互相覆盖面积。6.2 中文乱码和 JSON 文件格式问题json-server 默认读取和写入 db.json 时都按 UTF-8 处理正常情况下中文不会乱码。但 Windows 环境下如果 db.json 文件本身不是 UTF-8 编码启动后接口返回的中文就会变成乱码。遇到这种情况用编辑器把文件重新保存为 UTF-8 without BOM 格式即可。更隐蔽的问题是 BOM。有些编辑器保存 UTF-8 文件时会带上 BOM 头json-server 解析时会报错或者把第一个 key 名解析出特殊字符。我遇到过启动后/posts路由正常但/Ϊposts多出一个诡异路由的情况最后发现是文件带 BOM。建议项目里统一用 VS Code 或代码格式化工具保存时约定 UTF-8。JSON 格式还有一个常见坑多人手工编辑 db.json 时容易在数组末尾多写一个逗号。严格模式下 JSON 不允许尾逗号会导致服务启动失败。规范的做法是不直接编辑 db.json而是维护一个 seed 脚本通过脚本生成。6.3 端口占用和 watch 模式失效默认端口 3000 经常被别的开发服务占用。启动时报EADDRINUSE错误时不需要慌换一个端口就行npx json-server db.json --port 4000更推荐的方式是把它写进 npm scripts统一固定在 mock 端口上。比如{ scripts: { mock: json-server mock/db.json -p 4000 -w -r mock/routes.json -m mock/middleware.js } }watch 模式失效一般是因为 db.json 被软链接指向了项目外的共享目录json-server 监听不到。如果希望外部目录变化能触发更新最好把 db.json 放在服务启动目录下或者直接重启服务。我自己很少依赖热重载因为改数据之后往往要同时刷新页面看效果服务自动重载反而会让连续操作出现一瞬间的空窗手动重启控制感更强。6.4 我在团队里最终落地的工作流经过这些折腾我在不同项目里沉淀了一套固定打法目前用下来比较省心。如果你刚接触 json-server可以直接按这个路径搭建在项目根目录建mock/文件夹里面放db.json、routes.json、middleware.js、generate.js四个文件。用generate.js统一造数保证数据可再生成、可审查。用routes.json统一加/api前缀让 mock 接口风格和真实后端保持一致。在middleware.js里写统一的延迟和模拟鉴权逻辑。把启动命令写进package.json的scripts团队共享。开发环境里前端请求 baseURL 指向 mock 服务联调时切换环境变量指向真实后端。这套流程最大的好处是“环境切换成本几乎为零”。前端代码不需要知道 mock 服务存在因为 URL 永远是/api/posts这种形式只是环境变量里的 host 不同。我的同事接手这类项目时只需要运行npm run mock再开一个前端 dev server就能在不依赖真实后端的情况下继续开发。最后再分享一个小技巧如果你在写一个完整的前端演示项目希望别人在本地一条命令就能跑起来可以把 mock 服务和前端 dev server 做成 parallel 启动用 concurrently 之类的工具同时拉起。这样别人 clone 下项目执行一条命令就能看到完整页面效果。json-server 虽然叫“零代码后端模拟神器”但真正把它用顺手靠的是把它嵌进整个开发流程里让团队的协作方式适应它。我的建议是任何一个项目在第一天就配好 mock 环境而不是等后端接口延期了才去补。