目前来说,前后端分离 是web开发的主流模式。前端负责页面制作、交互实现,后端负责数据API的开发。两者并行处理,各司其职,项目开发完毕后一起联调,出了问题也能快速定位。

而通常情况下,后端真实数据的研发往往滞后于UI界面的开发。所以,在项目开始前,前后端会约定好数据格式,这样,前端就可以先利用mock数据进行界面开发。一般的做法,是在本地新建多个json文件,然后利用ajax请求相应的文件,待后端数据接口开发完毕,直接将json地址换成真实接口地址即可。

但还是会有如下问题:

  • 本地要建多个json数据文件
  • 为接近真实场景,要生成多条数据,还要随机模拟内容
  • 无法处理分页请求
  • 除 GET,POST、PUT、PATCH、DELETE等请求,无法直接在浏览器操作

为解决上述问题,你需要一个数据服务器,它就是 JSON Server。

一、安装与启动

JSON Server 是一个快速构建测试数据的 REST API 服务器。通过它,我们可以在短时间内创建多组测试数据,它允许我们自定义请求数据的路由,支持 GET,POST、PUT、PATCH、DELETE 等多种请求。另外,我们还可以跨域访问 JSON Server 构建的数据API。

安装

通过如下命令安装:

> npm install -g json-server

我使用这款工具时,它的最新版本是 0.10.1。安装完,我们可以通过以下命令,来查看相关信息:

> json-server -h

CLI 输出的信息:

其中包括配置文件、端口、主机、监听文件、路由设置、中间件,静态文件、数据只读等设置,在右边可以看到它们的默认值。

启动

要启动 JSON Server 数据服务,首先,得创建一个数据文件。我们在任意位置新建一个文件夹 json-server,进入该目录,再新建一个 db.json 数据文件 :

> mkdir json-server && cd json-server 
> cd.>db.json

打开 db.json,加入内容:

{
"posts": [
{"id": 1, "title": "title one", "body": "post ab"},
{"id": 2, "title": "title two", "body": "post cd"}
],
"comments": [
{"id": 1, "postId": 1, "email": "ab@xx.com", "body": "comment ab"},
{"id": 2, "postId": 1, "email": "cd@xx.com", "body": "comment cd"},
{"id": 3, "postId": 2, "email": "ef@xx.com", "body": "comment ef"}
]
}

接着,通过命令将数据文件与服务关联,可通过以下命令启动服务(db.json 里的内容一定要符合 JSON 格式,否者服务器会报错。):

> json-server --watch db.json

将看到:

此时,打开 http://localhost:3000, 便能看到一个关于本地 JSON Server 的首页,里面包含的 Resources 项,便是我们创建的接口。

Resources

/posts 2x
/comments 3x
/db state

/db 是总的数据接口,/posts/comments 分别是子接口,直接打开可访问到相应的数据。

这个 JSON Server 首页,我们也可以自定义。只需要在根目录下创建 ./public 文件夹,并在里面新建一个 index.html 页面即可。

二、数据操作

前面说到,JSON Server 支持多种请求方式,并支持跨域请求。我们可以在其他项目中去请求上面的数据服务,来看下它们对应的数据操作,这里假设你使用的是 Jquery,版本2.1.3:

请求方式

GET

GET 请求用于读取数据,以下内容通过不同参数请求不同接口:

var root = 'http://localhost:3000';

$.ajax({
url: root + '/posts/1',
method: 'GET'
}).then(function(res) {
console.log(res);
});
// 返回数据: {"id": 1, "title": "title one", "body": "post ab"}

$.ajax({
url: root + '/posts',
method: 'GET',
data: {
id: 1
}
}).then(function(res) {
console.log(res);
});
// 返回数据: [{"id": 1, "title": "title one", "body": "post ab"}]

$.ajax({
url: root + '/comments',
method: 'GET'
}).then(function(res) {
console.log(res);
});

/* 返回数据:
[
{"id": 1, "postId": 1, "email": "ab@xx.com", "body": "comment ab"},
...
{"id": 6, "postId": 3, "email": "kl@ab.com", "body": "comment kl"},
*/

除了基本查询,JSON Server 还支持多条件组合查询,详见 条件查询 章节。

POST

POST 请求一般用于新增内容,以下为新增一篇文章:

var root = 'http://localhost:3000';

$.ajax({
url: root + '/posts',
method: 'POST',
data: {
title: 'title three',
body: 'post ef'
}
}).then(function(res) {
console.log(res);
});

// {"title": "title three", "body": "post ef", "id": 3}

注意,对于一些版本的 jquery,需要在请求头添加 Content-Type: application/json,否者 POST,PUT,PATCH 请求会被处理成 GET,不会对数据做任何改变 。

POST 请求发送完,ajax 回调函数将返回 db.json 存储的数据项。此时,打开 db.json 文件,你会发现数据项已经被写入到 db.json 中。

可以看到,倘若没有指定数据的 id 属性,则 JSON Server 会根据已有数据最大的 id 值进行自增。

倘若你 POST 的数据项里已经指定 id 的值,则存储到 db.json 里的字段值会变成字符串,比如你发送的数据为:

{id: 4, title: 'title four', body: 'post gh', flag: true}

则存储数据为:

{"id": "4", "title": "title four", "body": "post gh", "flag": "true"}

可以看到,id 和 flag 的值都被转换为字符串。

另外,对于提交相同 id 的数据,服务端会提示报错: Error: Insert failed; duplicate id.

DELETE

DELETE 请求一般用于删除内容,以下为删除 id 为 4 的文章:

var root = 'http://localhost:3000';

$.ajax({
url: root + '/posts/4',
method: 'DELETE'
}).then(function(res) {
console.log(res);
});

// []

上面的 DELETE 请求返回一个空数组,表示删除成功。打开 db.json 文件,可以看到,第4篇文章已经被删除。

PUT

PUT 请求可更新或新增一项数据。它的具体作用是,当请求的数据存在,则更新该数据。当请求的数据不存在,则新增一项数据。以下分别为更新 id 为 1 的文章:

var root = 'http://localhost:3000';

$.ajax({
url: root + '/posts/1',
method: 'PUT',
data: {
id: 1,
title: 'title one one',
body: 'post ab ab'
}
}).then(function(res) {
console.log(res);
});

// {"id": 1, "title": "title one one", "body": "post ab ab"}

但我尝试使用 PUT 发送新数据时,却出现 404 报错的情况。

PATCH

PATCH 请求是对 PUT 的补充,用于数据项的局部更新,以下为更新 id 为 2 的 body 属性值:

var root = 'http://localhost:3000';

$.ajax({
url: root + '/posts/2',
method: 'PATCH',
data: {
body: 'post cd cd'
}
}).then(function(res) {
console.log(res);
});

// {"id": 2, "title": "title two", "body": "post cd cd"}

对 JSON Server 数据服务发送 增删改查 的请求,它里面的数据会作相应的更新,这种更新是基于 lowdb

条件查询

JSON Server 提供了多种形式的条件查询,首先,在 db.json 里弄点数据,5篇文章、3条评论:

{
"posts": [
{
"id": 1,
"title": "aa",
"body": "post aa",
"tag": ["a1", "a2"],
"likes": 1000,
"author": {
"name": "tom",
"email": "tom@xx.com"
}
},
{
"id": 2,
"title": "bb",
"body": "post bb ttt",
"tag": ["b1"],
"likes": 500,
"author": {
"name": "jack",
"email": "jack@xx.com"
}
},
{
"id": 3,
"title": "cc",
"body": "post cc",
"tag": ["c1", "c2", "c3"],
"likes": 1200,
"author": {
"name": "lucy",
"email": "lucy@xx.com"
}
},
{
"id": 4,
"title": "dd",
"body": "post dd",
"tag": ["d1"],
"likes": 20,
"author": {
"name": "lucy",
"email": "lucy@xx.com"
}
},
{
"id": 5,
"title": "ee",
"body": "post ee",
"tag": ["e1"],
"likes": 100,
"author": {
"name": "joy",
"email": "joy@xx.com"
}
}
],
"comments": [
{
"id": 1,
"postId": 1,
"email": "ab@xx.com",
"body": "comment ab"
},
{
"id": 2,
"postId": 1,
"email": "cd@xx.com",
"body": "comment cd"
},
{
"id": 3,
"postId": 2,
"email": "ef@xx.com",
"body": "comment ef"
}
]
}

然后,再来看看每个查询操作:

过滤(Filter)

除了普通参数,还可通过 . 来访问深层属性:

相同参数(id)取或,不同参数(author.name、title)取与。

分页(Paginate)

可使用参数 _page_limit(可选),来返回分页数据。不用 _limit 时,默认返回 10 条。

截取(Slice)

Slice 截取的规则和数组中的 slice 方法相同。可使用参数 _start_end 以及 _limit ,来返回截取数据。

包括 _start 的文章,但不包括 _end 的文章。可以把 _start_end 看成数组项的索引值。

分类排序(Sort)

可使用参数 _sort_order,来返回排序的数据。

运算(Operators)

可使用参数 _gte_lte,来返回一个范围的数据。

可使用参数 _ne,来返回除指定之外的数据。

全文搜索(Full-text search)

可使用参数 q,来返回包含指定字符的数据。它会匹配每项数据的所有字段。

关联(Relationships)

可使用参数 _embed,将关联子数据拼接起来。

返回数据为:

{
"id": 1,
"title": "aa",
"body": "post aa",
"tag": ["a1", "a2"],
"likes": 1000,
"author": {
"name": "tom",
"email": "tom@xx.com"
},
"comments": [
{
"id": 1,
"postId": 1,
"email": "ab@xx.com",
"body": "comment ab"
},
{
"id": 2,
"postId": 1,
"email": "cd@xx.com",
"body": "comment cd"
}
]
}

返回的数据为:

{
"id": 1,
"postId": 1,
"email": "ab@xx.com",
"body": "comment ab",
"post": {
"id": 1,
"title": "aa",
"body": "post aa",
"tag": ["a1", "a2"],
"likes": 1000,
"author": {
"name": "tom",
"email": "tom@xx.com"
}
}
}

如果你觉得自己建立一个数据服务器,创建数据这些操作麻烦。希望可以直接调用数据,那也是没问题的,只需要通过 JSONPlaceholder。

三、JSONPlaceholder

JSONPlaceholder 可以说是 JSON Server 的一个线上例子,它是由 JSON Server 的作者开发的,正如它所建立的初衷:

Most of the time when trying a new library, hacking a prototype or following a tutorial, I found myself in need of some data.

I didn’t like the idea of using some public API because I had the feeling that I was spending more time registering a client and understanding a complex API than focusing on my task.

But I liked the idea of image placeholders for web designers. So I decided to code a little Express server inspired by that and here is JSONPlaceholder.

作者在开发一些项目时,需要若干测试数据。但网上一些公用的数据接口,需要注册,还要花时间了解流程以及API的使用方法。可是他只想专注项目本身的开发,于是,它对照web设计师所使用的 image placeholder,再结合 JSON Server,创建了 JSONPlaceholder。

基于大多数项目的常见需求,它提供了一套可用的API数据,用户直接调用即可。并且它有如下特点:

  • 免注册
  • 零设置(编码)
  • API简单
  • 关联性强
  • 资源过滤和嵌套
  • 跨域(CORS and JSONP)
  • 支持 GET, POST, PUT, PATCH, DELETE 和 OPTIONS 请求
  • 与 Backbone, AngularJS, Ember 等库兼容

它包含的数据API有:

接口 数据条数
/posts 100
/comments 500
/albums 100
/photos 5000
/todos 200
/users 10

并且,你还能使用不同的请求方式去请求不同的路由:

请求方式 接口 描述
GET /posts 获取所有文章
GET /posts/1 获取第一篇文章
GET /posts/1/comments 通过 posts,查询第一篇文章所有的评论
GET /comments?postId=1 通过 comments,查询第一篇文章所有的评论
GET /posts?userId=1 获取 userId=1 发布的文章
POST /posts 新增一片文章
PUT /posts/1 更新第一篇文章,没有相应的文章,则创建它
PATCH /posts/1 更新第一篇文章的某个属性
DELETE /posts/1 删除第一篇文章

而你要做的,只需要通过如下方式调用它即可(假如你用的Jquery):

var root = 'http://jsonplaceholder.typicode.com';

// 每页限制5条,获取第二页文章(6-10)
$.ajax({
url: root + '/posts?_page=2&_limit=5',
method: 'GET'
}).then(function(res) {
console.log(res);
});

// 新增一篇文章
$.ajax({
url: root + '/posts',
method: 'POST',
data: {
title: 'fooooooo',
body: 'barrrrr',
userId: 1
}
}).then(function(res) {
console.log(res);
});

// 删除一篇文章
$.ajax({
url: root + '/posts/1',
method: 'DELETE'
}).then(function(res) {
console.log(res);
});

更多用法可参见,使用实例

注意,你向 http://jsonplaceholder.typicode.com 发送的新增、修改、删除 的请求并不会真的对 jsonplaceholder 服务器上的数据执行 新增、修改、删除 的操作,它只是做了一个模拟请求。因此,如果你希望接近真实的数据操作,还是得通过 JSON Server 去创建一个数据服务。

四、JSON Server 深入

除了上面说到的内容外,JSON Server 还有一些相对高级的特性:

命令行设置

自定义访问目录

默认情况下,服务器本地有一个 JSON Server 首页。在文章前面有提到,我们可以在根目录下新建 ./public 目录,然后,在里面新建一个 index.html 便可自定义这个首页,除了 index.html 这个页面,我们还可以在 ./public 中放 js、images等文件。

./public 是默认指定的文件名。

若你不想使用 ./public 这么一个文件名,你也可通过 --static 命令手动指定其他文件夹。比如,在根目录新建 ./demo 目录, 要把 ./demo 作为访问目录。只需如下命令:

> json-server --watch db.json --static demo

由于 ./demo 被当做了服务器访问的目录,因此启动后,通过 http://localhost:3000 便能访问 ./demo 目录的内容。

设置端口号

默认情况下,JSON Server 服务器的端口号是 3000,我们也可以手动设置,如下:

> json-server --watch db.json --port 9999

这里将端口号设置为 9999,打开 ,便能访问到 JSON Server 首页。

整合命令

除了上面说到的两个命令,JSON Server 还提供了其他多个命令。为了避免在 CLI 中输入一串参数,我们可以在根目录新建 json-server.json 文件来覆盖默认设置:

// json-server.json
{
"port": "9999",
"watch": true,
"delay": 1000,
"static": "./demo"
}

此时,运行 json-server db.json 即可启动服务,并让上述设置生效。

创建动态数据

除了手动新建 db.json 作为数据文件,我们还可以通过运行js文件来生成动态数据。在根目录新建 data.js 文件,加入代码:

// data.js
module.exports = () => {
const data = {users: [] };
// Create 50 users
for (let i = 0; i < 50; i++) {
data.users.push({ id: i, name: `user${i}` });
}
return data;
};

运行 json-server data.js,访问 http://localhost:9999/users, 便能看到生成的数据(存在内存):

[
{"id": 0, "name": "user0"},
{"id": 1, "name": "user1"},
...
{"id": 49, "name": "user49"}
]

自定义路由

我们并非一定要按照 db.json 定义的数据结构来访问相应的接口,可通过在根目录创建一个 routes.json 文件来自定义访问路由:

// routes.json
{
"/api/p": "/posts",
"/api/c": "/comments",
"/api/:resource?id=:id": "/:resource/:id"
}

json-server.json 文件中新增路由指定:

// json-server.json
{
...
"routes": "./routes.json"
}

运行 json-server db.json 重启服务,此时,访问左边的地址,等价于访问右边的:

新路由 旧路由
http://localhost:9999/api/p http://localhost:9999/posts
http://localhost:9999/api/c http://localhost:9999/comments
http://localhost:9999/api/p?id=1 http://localhost:9999/posts/1

中间件

为了让数据访问功能更强大、更灵活,JSON Server 提供了中间件,用户可在数据返回前,进行相关操作。比如,下面这段代码便是在数据返回前,进行相关验证:

// server.js
const jsonServer = require('json-server')
const server = jsonServer.create()
const router = jsonServer.router('db.json')
const middlewares = jsonServer.defaults()

server.use(middlewares)
server.use((req, res, next) => {
if (isAuthorized(req)) { // 增加验证代码
next() // 继续执行 JSON Server 路由
} else {
res.sendStatus(401)
}
})
server.use(router)
server.listen(3000, () => {
console.log('JSON Server is running')
})

新增 server.js 文件后,你只需要运行 node server.js, 便可返回数据前进行相关验证。