☰
json-server 前端数据模拟实战:从零搭建 RESTful 接口
2026/10/17 3:31:57 网站建设 项目流程

1. 为什么前端开发离不开数据模拟工具

做过前端的人都有一个共同的痛:页面写完了,接口还没好。后端同事说“再等等,这周排期满了”,产品经理说“明天先给我看看效果”,你夹在中间,要么干等,要么自己造数据。早期我的做法很原始——在代码里写死一个数组,或者用setTimeout模拟一下延迟,返回一段静态 JSON。这种方式在页面少、字段少的时候还能凑合,一旦接口数量上来了,字段结构复杂了,增删改查都要模拟,代码里就会堆满各种临时变量和注释,最后自己都分不清哪些是真实逻辑、哪些是临时补丁。

后来我开始找更系统的方案。试过在本地起一个简单的 Node 服务,手写路由返回 JSON 文件;也试过用一些在线的接口模拟平台,但要么需要联网、要么有调用次数限制、要么配置起来特别繁琐。直到用上json-server,才算真正找到了一个平衡点:它足够轻,一条命令就能跑起来;它足够真,支持完整的 RESTful 接口语义;它足够灵活,数据用 JSON 文件管理,改起来毫无负担。这篇文章我就围绕这个工具,把从零搭建、数据建模、进阶技巧到踩坑经验完整梳理一遍,不管你是刚接触前端的新手,还是想优化本地开发流程的老手,都能直接拿去用。

2. json-server 到底是什么,能解决哪些实际问题

2.1 一句话理解它的定位

json-server 是一个基于 Node.js 的命令行工具,你给它一个 JSON 文件,它就能在本地启动一个完整的 RESTful API 服务。这个 JSON 文件里的每一个顶层字段,都会被自动映射成一组接口。比如你有一个db.json,里面有个users数组,那么启动后你就自动拥有了GET /users、GET /users/1、POST /users、PUT /users/1、PATCH /users/1、DELETE /users/1这一整套接口。不需要写一行服务端代码,不需要配置路由表,不需要定义 Controller。

我习惯把它类比成“前端开发者的临时数据库加接口服务器”。它不像真正的数据库那样需要安装、建表、写 SQL,也不像 Mock 平台那样需要登录、创建项目、配置字段。它就是“一个文件换一套接口”,简单直接。

2.2 它解决的核心痛点

在没有 json-server 之前,前端对接接口通常面临几个典型问题。第一是接口不可用,后端还没写完,前端只能等或者自己造。第二是数据结构不稳定,后端字段改来改去,前端跟着反复调整。第三是联调环境复杂,有时候测试环境挂了,整个团队都停摆。第四是增删改查难以模拟,静态 JSON 只能读不能写,表单提交、删除操作没法验证。

json-server 针对性地解决了这些问题。它让前端在接口定义阶段就能独立开发,数据结构由前端自己掌控,增删改查全部支持且数据会持久化到 JSON 文件里。更重要的是,它启动成本极低,一条npx json-server --watch db.json就能跑起来,不需要额外安装数据库或配置环境变量。

2.3 适合哪些人用

这个工具几乎覆盖了所有需要跟接口打交道的前端场景。个人开发者做 side project 时,没有后端资源,用它最合适。团队开发中,前后端并行时,前端可以先用它模拟接口,等后端好了再切换 baseURL。教学场景下,老师讲解 Ajax、Fetch、Axios 时,用它提供稳定的数据源。甚至测试同学也可以用它在本地复现特定数据状态,验证边界情况。

注意:json-server 定位是开发阶段的模拟工具,不适合直接用于生产环境。它的性能、安全性、并发能力都没有经过生产级验证,这一点后面会详细说。

3. 从零搭建:环境准备与第一个模拟接口

3.1 安装方式的选择与理由

json-server 的安装有两种主流方式:全局安装和项目内安装。全局安装用npm install -g json-server,好处是任何目录下都能直接敲json-server命令,适合快速试验。项目内安装用npm install json-server --save-dev,好处是版本锁定在项目里,团队成员拉下代码后npm install就能得到一致的版本,不会出现“你那边能跑我这边报错”的情况。

我个人的建议是:如果是正式项目,一律用项目内安装,然后在package.json的scripts里加一条命令,比如"mock": "json-server --watch mock/db.json --port 3001"。这样团队里任何人执行npm run mock就能启动,不需要额外文档说明。如果是临时想试一下,用npx json-server最省事,不污染全局环境。

3.2 最小可用示例:三分钟跑起来

先创建一个db.json文件,内容如下:

{ "users": [ { "id": 1, "name": "张三", "email": "zhangsan@example.com", "role": "admin" }, { "id": 2, "name": "李四", "email": "lisi@example.com", "role": "editor" } ], "posts": [ { "id": 1, "title": "第一篇帖子", "authorId": 1, "published": true }, { "id": 2, "title": "第二篇帖子", "authorId": 2, "published": false } ] }

然后在终端执行:

npx json-server --watch db.json --port 3001

你会看到终端输出类似这样的信息:

\{^_^}/ hi! Loading db.json Done Resources http://localhost:3001/users http://localhost:3001/posts Home http://localhost:3001

这时候打开浏览器访问http://localhost:3001/users,就能看到完整的用户列表 JSON。访问http://localhost:3001/users/1得到单个用户。用 Postman 或 curl 发一个 POST 请求到/users,传入 JSON body,就会新增一条记录,并且这个记录会被写回db.json文件,下次启动依然存在。

3.3 目录结构建议

随着模拟数据变多,把所有内容塞在一个db.json里会变得难以维护。我通常会在项目根目录建一个mock文件夹,里面这样组织:

mock/ db.json # 主数据文件 routes.json # 自定义路由映射 middlewares.js # 自定义中间件 seed/ # 初始数据种子 users.json posts.json

然后在db.json里通过引用或手动合并的方式组织数据。json-server 本身不直接支持文件拆分,但可以通过--watch配合脚本在启动前合并,或者用js文件作为数据源(后面会讲)。这种结构在项目变大后优势明显,每个模块的数据独立管理,改用户数据不会误碰帖子数据。

4. 核心功能深度拆解:RESTful 语义与数据操作

4.1 自动生成的路由规则

json-server 最核心的能力就是根据 JSON 顶层字段自动生成路由。假设db.json里有users、posts、comments三个数组,那么自动生成的接口包括:

操作方法路径说明
列表GET/users返回全部用户
详情GET/users/:id返回指定 id 的用户
新增POST/users创建新用户,id 自动生成
全量更新PUT/users/:id替换指定用户全部字段
局部更新PATCH/users/:id只更新传入的字段
删除DELETE/users/:id删除指定用户

这套规则完全遵循 RESTful 规范,前端用 Axios 或 Fetch 调用时,跟调用真实后端接口的写法一模一样。这意味着你后期切换真实接口时,只需要改baseURL,业务代码几乎不用动。

4.2 查询参数:过滤、排序、分页、全文搜索

json-server 支持丰富的查询参数,这是它比手写静态 JSON 强大得多的地方。我整理了一张常用参数表:

参数示例效果
字段过滤/users?role=admin返回 role 为 admin 的用户
模糊匹配/users?name_like=张name 包含“张”的用户
范围查询/posts?id_gte=2&id_lte=5id 在 2 到 5 之间
排序/users?_sort=name&_order=asc按 name 升序
分页/users?_page=1&_limit=10第一页,每页 10 条
全文搜索/users?q=张所有字段中包含“张”的记录
关联查询/posts?authorId=1查询 authorId 为 1 的帖子

这些参数可以组合使用,比如/posts?_sort=title&_order=desc&_page=2&_limit=5表示按标题降序、取第二页每页五条。分页时响应头里还会带上X-Total-Count,方便前端计算总页数。

实操心得:_like是大小写不敏感的模糊匹配,但只对字符串字段生效。如果你需要对数字字段做模糊匹配,得先转成字符串,或者用_gte/_lte做范围查询。

4.3 关联数据与嵌套资源

json-server 支持通过_embed和_expand做简单的关联查询。假设posts里有authorId指向users的id,那么:

  • GET /posts?_embed=comments会把每条帖子关联的 comments 嵌套进去
  • GET /comments?_expand=post会把每条评论关联的 post 展开

这种关联是“约定优于配置”的:json-server 会自动识别xxxId这样的外键字段。比如comments里有postId,那么_embed=comments就能在 post 里嵌入对应的评论列表。这个功能在模拟“文章详情页带评论”这类场景时特别有用,不需要手动拼数据。

4.4 数据持久化机制

json-server 默认会把所有写操作(POST、PUT、PATCH、DELETE)的结果写回db.json文件。这意味着你通过接口新增的数据,重启服务后依然存在。这个特性有利有弊:好处是模拟状态可以累积,比如你测试分页时可以先造 100 条数据;坏处是测试过程中产生的脏数据会污染初始数据,下次演示时可能看到一堆乱七八糟的记录。

我的做法是:把干净的初始数据放在seed/db.json,启动前用脚本复制到mock/db.json,这样每次启动都是干净状态。或者用--watch监听一个只读的种子文件,写操作写到内存里不落盘(需要自定义配置)。后面进阶部分会给出具体方案。

5. 进阶配置:让模拟服务更贴近真实后端

5.1 自定义端口与主机

默认端口是 3000,但前端项目通常也用 3000,容易冲突。用--port指定其他端口,比如--port 3001。如果想让局域网内其他设备访问(比如手机真机调试),加--host 0.0.0.0,然后通过本机 IP 访问。这个在移动端调试时非常实用,我经常用手机连同一个 WiFi 直接访问电脑上的模拟接口。

5.2 自定义路由映射

真实后端的接口路径往往带前缀,比如/api/v1/users。json-server 支持通过--routes指定一个路由配置文件:

{ "/api/*": "/$1", "/v1/*": "/$1" }

这样访问/api/users就会被映射到/users。前端代码里写/api/users,后期切换真实接口时不用改路径。这个配置在团队约定接口前缀时特别有用。

5.3 自定义中间件:模拟延迟、鉴权、错误码

json-server 支持通过--middlewares加载自定义中间件。中间件就是一个函数,接收req、res、next三个参数。我常用的几个场景:

模拟网络延迟:

module.exports = (req, res, next) => { setTimeout(next, 800); };

模拟鉴权失败:

module.exports = (req, res, next) => { if (req.path === '/users' && req.method === 'POST') { if (!req.headers.authorization) { return res.status(401).json({ error: '未授权' }); } } next(); };

模拟特定错误码:

module.exports = (req, res, next) => { if (req.path === '/error-test') { return res.status(500).json({ error: '服务器内部错误' }); } next(); };

这些中间件让模拟服务不再是“永远成功”的理想状态,前端可以提前处理加载中、超时、鉴权失败、服务端错误等各种边界情况。这一点在实际开发中价值极高,很多 bug 都是在异常流程里暴露的。

5.4 用 JavaScript 文件作为数据源

除了 JSON 文件,json-server 还支持.js文件作为数据源。这意味着你可以用代码动态生成数据:

module.exports = () => { const data = { users: [], posts: [] }; for (let i = 1; i <= 100; i++) { data.users.push({ id: i, name: `用户${i}`, email: `user${i}@example.com`, role: i % 3 === 0 ? 'admin' : 'editor' }); } return data; };

启动时用json-server --watch db.js。这种方式适合需要批量造数据的场景,比如测试分页、测试列表渲染性能。我试过生成 1000 条用户数据,页面滚动加载的表现一目了然。

6. 实操全流程:一个完整的模拟项目搭建记录

6.1 项目初始化与依赖安装

假设我们有一个前端项目,需要模拟用户管理和文章管理两个模块。首先在项目根目录执行:

npm init -y npm install json-server --save-dev

然后在package.json里添加脚本:

{ "scripts": { "mock": "json-server --watch mock/db.json --port 3001 --routes mock/routes.json --middlewares mock/middlewares.js" } }

6.2 数据建模与种子数据准备

创建mock/db.json,设计用户和文章的数据结构:

{ "users": [ { "id": 1, "name": "张三", "email": "zhangsan@example.com", "role": "admin", "createdAt": "2024-01-15" }, { "id": 2, "name": "李四", "email": "lisi@example.com", "role": "editor", "createdAt": "2024-02-20" }, { "id": 3, "name": "王五", "email": "wangwu@example.com", "role": "viewer", "createdAt": "2024-03-10" } ], "posts": [ { "id": 1, "title": "前端工程化实践", "authorId": 1, "status": "published", "views": 1200 }, { "id": 2, "title": "CSS 布局技巧", "authorId": 2, "status": "draft", "views": 0 }, { "id": 3, "title": "JavaScript 异步编程", "authorId": 1, "status": "published", "views": 850 } ], "comments": [ { "id": 1, "postId": 1, "content": "写得很实用", "userId": 2 }, { "id": 2, "postId": 1, "content": "期待后续", "userId": 3 }, { "id": 3, "postId": 3, "content": "讲得很清楚", "userId": 2 } ] }

这里我特意设计了几个关联关系:posts.authorId指向users.id,comments.postId指向posts.id,comments.userId指向users.id。这样后面可以用_embed和_expand做关联查询。

6.3 路由与中间件配置

创建mock/routes.json:

{ "/api/*": "/$1" }

创建mock/middlewares.js:

module.exports = (req, res, next) => { // 模拟 500ms 网络延迟 setTimeout(() => { // 模拟鉴权:非 GET 请求需要 token if (req.method !== 'GET' && !req.headers['x-token']) { return res.status(401).json({ code: 401, message: '缺少鉴权令牌' }); } next(); }, 500); };

6.4 启动与验证

执行npm run mock,终端显示服务已启动。然后用 curl 验证几个关键接口:

# 获取用户列表 curl http://localhost:3001/api/users # 获取文章并嵌入评论 curl http://localhost:3001/api/posts?_embed=comments # 获取评论并展开文章 curl http://localhost:3001/api/comments?_expand=post # 新增用户(带鉴权头) curl -X POST http://localhost:3001/api/users \ -H "Content-Type: application/json" \ -H "x-token: test" \ -d '{"name":"赵六","email":"zhaoliu@example.com","role":"editor"}' # 不带鉴权头,应该返回 401 curl -X POST http://localhost:3001/api/users \ -H "Content-Type: application/json" \ -d '{"name":"测试"}'

实测下来,所有接口都按预期工作。新增的用户会写回db.json,刷新列表能看到。401 鉴权也正常触发。这套配置已经足够支撑一个中型前端项目的模拟需求。

6.5 前端代码对接示例

以 Axios 为例,前端封装一个请求模块:

import axios from 'axios'; const api = axios.create({ baseURL: 'http://localhost:3001/api', timeout: 5000, headers: { 'Content-Type': 'application/json', 'x-token': 'dev-token' } }); export const getUsers = (params) => api.get('/users', { params }); export const getUser = (id) => api.get(`/users/${id}`); export const createUser = (data) => api.post('/users', data); export const updateUser = (id, data) => api.patch(`/users/${id}`, data); export const deleteUser = (id) => api.delete(`/users/${id}`); export const getPostsWithComments = () => api.get('/posts?_embed=comments');

后期切换真实后端时,只需要把baseURL改成真实地址,其他代码不动。这就是 json-server 最大的价值:让前端开发与后端解耦,同时保持接口调用方式的一致性。

7. 常见问题与排查技巧实录

7.1 端口被占用怎么办

启动时报EADDRINUSE,说明端口被其他程序占了。最简单的办法是换端口,--port 3002。如果想知道谁占了端口,Windows 下用netstat -ano | findstr :3000,Mac 或 Linux 下用lsof -i :3000。我通常会在项目里固定用 3001 到 3010 之间的端口,避开前端常用的 3000 和 8080。

7.2 数据写入后没有持久化

json-server 默认会持久化,但如果你用的是.js数据源,写操作不会落盘,因为 JS 文件是动态生成的。另外,如果db.json文件权限不足,写入也会失败。检查文件是否可写,以及启动命令是否带了--watch。不带--watch时,json-server 只读不写。

7.3 关联查询不生效

_embed和_expand依赖外键命名约定。如果posts里的外键叫author_id而不是authorId,关联就会失败。json-server 识别的是xxxId这种驼峰命名。如果你的数据字段是下划线风格,需要手动转换或者在中间件里做映射。

7.4 分页总数不对

分页时响应头X-Total-Count返回的是过滤后的总数,不是全量总数。如果你先过滤再分页,这个值是准确的。但如果你需要全量总数,得单独发一个不带分页参数的请求。另外,跨域时浏览器默认拿不到这个响应头,需要在服务端配置Access-Control-Expose-Headers,json-server 默认已经暴露了常用头,一般不需要额外配置。

7.5 中文搜索乱码

URL 里的中文需要编码,用encodeURIComponent处理。比如搜索“张三”,实际请求路径应该是/users?name_like=%E5%BC%A0%E4%B8%89。Axios 会自动处理参数编码,手动拼 URL 时要注意。

7.6 常见问题速查表

问题现象可能原因解决方法
启动报端口占用端口被其他程序使用换端口或结束占用进程
写操作不生效未加 --watch 或文件只读加 --watch,检查文件权限
关联查询为空外键命名不符合约定改为 xxxId 驼峰命名
分页总数不对过滤条件影响总数单独请求全量数据计数
中文搜索无结果URL 未编码使用 encodeURIComponent
跨域请求失败浏览器同源策略json-server 默认支持 CORS,检查请求头
数据被污染测试写入脏数据启动前从种子文件复制

避坑技巧:我习惯在mock目录下放一个reset.js脚本,每次启动前执行node mock/reset.js把种子数据复制到db.json。这样无论上次测试写了多少脏数据,下次启动都是干净的。脚本内容很简单,就是fs.copyFileSync两行代码。

8. 工具选型对比与适用边界

8.1 json-server 与其他方案的对比

市面上模拟数据的方案不少,我整理了一个对比表,方便你根据场景选择:

方案启动成本增删改查数据持久化关联查询适用场景
静态 JSON 文件极低不支持不涉及不支持纯展示页面
代码内硬编码低手动模拟不涉及手动拼临时演示
json-server低完整支持支持支持前后端并行开发
在线 Mock 平台中支持平台管理部分支持团队协作、需要分享
自建 Node 服务高完整支持需自行实现需自行实现复杂业务逻辑模拟

json-server 的优势在于“轻量但完整”。它不像在线平台那样需要注册登录、配置项目,也不像自建服务那样需要写大量代码。它就是一个文件加一条命令,但提供了足够真实的接口语义。

8.2 什么时候不该用 json-server

json-server 不是万能的。如果你的模拟需求涉及复杂的业务逻辑,比如“下单后扣库存、生成订单号、发送通知”这种多步骤流程,json-server 的默认能力就不够了,需要写大量中间件,反而不如直接写一个 Express 服务。另外,如果需要模拟 WebSocket、GraphQL、文件上传等非 RESTful 协议,json-server 也不支持。还有就是团队协作场景,如果多人需要共享同一套模拟数据,在线平台可能更方便,因为 json-server 的数据文件需要手动同步。

8.3 与前端框架的配合建议

在 Vue 或 React 项目里,我通常会把 json-server 的启动命令集成到开发脚本里。比如用concurrently同时启动前端和模拟服务:

{ "scripts": { "dev": "concurrently \"npm run mock\" \"npm run serve\"", "mock": "json-server --watch mock/db.json --port 3001", "serve": "vue-cli-service serve" } }

这样开发者只需要执行npm run dev,前端和模拟接口一起起来,体验非常顺畅。切换真实接口时,通过环境变量控制baseURL,比如.env.development里写VUE_APP_API_BASE=http://localhost:3001/api,生产环境改成真实地址。

9. 我在实际使用中积累的几个小技巧

第一个技巧是关于数据备份的。json-server 的写操作会直接修改db.json,有时候测试过程中不小心删了重要数据,恢复起来很麻烦。我的做法是把db.json纳入 Git 版本管理,每次启动前如果发现数据不对,直接git checkout mock/db.json就恢复了。同时把db.json加到.gitignore的例外里,确保它被跟踪。

第二个技巧是关于 ID 生成的。json-server 默认用自增数字作为新记录的 ID,但如果你的数据里已经有 ID 了,新增时会自动取最大值加一。这个行为在大多数情况下没问题,但如果你手动指定了字符串 ID,可能会冲突。建议统一用数字 ID,或者用 UUID 作为主键并在中间件里处理。

第三个技巧是关于性能的。当数据量超过几千条时,json-server 的查询会变慢,因为它是全量加载到内存再过滤。如果只是测试分页,不需要真的造几千条数据,用_page和_limit配合少量数据就能验证逻辑。真需要大数据量测试,建议用.js数据源动态生成,而不是写死在 JSON 文件里。

第四个技巧是关于调试的。json-server 启动后,访问根路径http://localhost:3001会看到一个简单的首页,列出了所有可用资源。这个页面在快速确认服务是否正常时很有用。另外,终端里会实时打印每个请求的方法和路径,排查问题时一眼就能看到前端到底发了什么请求。

这个工具我用了好几年,从个人项目到团队协作都离不开它。它不复杂,但足够解决前端开发中最常见的“等接口”问题。如果你还没用过,建议今天就花十分钟试一下,相信你会跟我一样,把它加入日常开发工具箱。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询