Why json-server?

在所有的项目开发中,前端开发都需要后台提供接口和数据,才能进行下一步的开发,但是后台开发多半是滞后于前端开发的,那么我们怎么才能自己临时地来模拟数据,并行开发呢?答案就是MOCK数据,对比过多种mock工具后,最终选择了使用 json server ,因为它足够简单,且足够强大,支持CORS和JSONP跨域请求,支持GET, POST, PUT, PATCH 和 DELETE 方法,更提供了一系列的查询方法,如limit,order等。

安装使用

npm install -g json-server

然后在根目录下面新建一个文件db.json:

{
  "posts": [
    { "id": 1, "title": "json-server", "author": "typicode" }
  ],
  "comments": [
    { "id": 1, "body": "some comment", "postId": 1 }
  ],
  "profile": { "name": "typicode" }
}

然后开始即可:

json-server --watch db.json

在浏览器里输入http://localhost:3000/posts/1你会看到下面的结果:

{ "id": 1, "title": "json-server", "author": "typicode" }

下面四点需要注意:

  • POST, PUT, PATCH ,DELETE等请求提交时,会使用lowdb自动改变db.json并保存
  • 所有提交的JSON数据都必须闭合在花括号里面
  • ID无法修改,只能使用POST新增
  • POST, PUT , PATCH等请求如果使用JSON数据提交时必需包含Content-Type: application/json,否则虽然会返回200但是数据不会改变

所有的使用选项如下:

json-server [options] <source>

Options:
  --config, -c       配置文件路径           [default: "json-server.json"]
  --port, -p         端口号                 [default: 3000]
  --host, -H         主机地址               [default: "localhost"]
  --watch, -w        是否监听文件           [boolean]
  --routes, -r       路由文件路径
  --middlewares, -m  中间件文件路径         [array]
  --static, -s       静态文件目录
  --read-only, --ro  只允许GET请求          [boolean]
  --no-cors, --nc    禁止跨域               [boolean]
  --no-gzip, --ng    禁止GZIP压缩           [boolean]
  --snapshots, -S    设置快照目录            [default: "."]
  --delay, -d        响应延迟(ms)
  --id, -i           设置数据库ID属性 (e.g. _id)   [default: "id"]
  --foreignKeySuffix, --fks  设置前缀, (e.g. _id as in post_id)
                                                   [default: "Id"]
  --quiet, -q        不输出日志信息                 [boolean]
  --help, -h         Show help                    [boolean]
  --version, -v      Show version number          [boolean]

Examples:
  json-server db.json
  json-server file.js
  json-server http://example.com/db.json

https://github.com/typicode/json-server

默认路由

  • 复数路由,针对数组数据:

GET    /posts
GET    /posts/1
POST   /posts
PUT    /posts/1
PATCH  /posts/1
DELETE /posts/1
  • 单数路由,针对对象数据:

GET    /profile
POST   /profile
PUT    /profile
PATCH  /profile
  • 过滤器,你可以使用.进行深层次的属性匹配:

GET /posts?title=json-server&author=typicode
GET /posts?id=1&id=2
GET /comments?author.name=typicode
  • 分页,默认是返回10条数据

GET /posts?_page=7
GET /posts?_page=1&_limit=2

返回除了获取的数据,在头部还会有一个Link头,包括了first, prev, next,last 链接:

<http://localhost:3000/posts?_page=1&_limit=1>; rel="first", <http://localhost:3000/posts?_page=1&_limit=1>; rel="prev", <http://localhost:3000/posts?_page=3&_limit=1>; rel="next", <http://localhost:3000/posts?_page=5&_limit=1>; rel="last"
  • 排序

GET /posts?_sort=views&_order=asc
GET /posts/1/comments?_sort=votes&_order=asc
GET /posts?_sort=user,views&_order=desc,asc
  • 切片

GET /posts?_start=20&_end=30
GET /posts/1/comments?_start=20&_end=30
GET /posts/1/comments?_start=20&_limit=10

除了返回数据外,在返回的消息头部,还有一个X-Total-Count字段,代表了所有的数据的总量

  • 操作符

_gte: 大于等于
_lte: 小于等于
_ne : 不等于
_like : 正则匹配
GET /posts?views_gte=10&views_lte=20
GET /posts?id_ne=1
GET /posts?title_like=server
  • 全文查询

使用q

GET /posts?q=internet
  • 关系

嵌入子属性使用_embed,嵌入父属性使用_expand:

GET /posts?_embed=comments
GET /posts/1?_embed=comments
GET /comments?_expand=post
GET /comments/1?_expand=post

获取或者创建嵌套属性:

GET  /posts/1/comments
POST /posts/1/comments
  • 获取所有数据

GET /db
  • 首页

GET /

其他

  • 静态文件服务器

只需要在根目录创建一个public的目录即可,或者使用--static手动设置别的目录

  • 使用远程数据

$ json-server http://example.com/file.json
$ json-server http://jsonplaceholder.typicode.com/db
  • 使用动态生成的数据

// index.js
module.exports = () => {
  const data = { users: [] }
  // Create 1000 users
  for (let i = 0; i < 1000; i++) {
    data.users.push({ id: i, name: `user${i}` })
  }
  return data
}
$ json-server index.js
  • 自定义路由(路由改写以及映射)

新建一个routes.json文件:

{
  "/api/*": "/$1",
  "/:resource/:id/show": "/:resource/:id",
  "/posts/:category": "/posts?category=:category",
  "/articles\\?id=:id": "/posts/:id"
}
json-server db.json --routes routes.json

现在这些路由就会被映射成:

/api/posts # → /posts
/api/posts/1  # → /posts/1
/posts/1/show # → /posts/1
/posts/javascript # → /posts?category=javascript
/articles?id=1 # → /posts/1

!!!注意,重要的事情说三遍:
!!!注意,重要的事情说三遍:
!!!注意,重要的事情说三遍:
上一个被映射的URL会重新匹配下面的映射规则,因此很有可能被再次映射:例如:

/api/posts/1  # → /posts/1

会继续匹配下面的/posts/javascript因而最终变成了/posts?category=1,和顺序有关,只会继续往下匹配改写,不会往上匹配,而且匹配后显示的URL并不改变,因此需要非常注意谨慎。

  • 使用中间件

// hello.js
module.exports = (req, res, next) => {
  res.header('X-Hello', 'World')
  next()
}
json-server db.json --middlewares ./hello.js
json-server db.json --middlewares ./first.js ./second.js
  • 使用配置文件

新建一个json-server.json或者使用--config, -c来指定:

{
  "port": 3000,
  "host": 'http://127.0.0.1'
  ...
}

模块使用

  • 安装使用

你也可以把json-server当做一个模块和其他的express中间件一起使用:

$ npm install json-server --save-dev
// 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(router)
server.listen(3000, () => {
  console.log('JSON Server is running')
})
$ node server.js

注意:jsonServer.router()也可以在现成的express项目中进行使用

  • 自定义json-server之外的路由

// Add custom routes before JSON Server router
server.get('/echo', (req, res) => {
  res.jsonp(req.query)
})
server.use((req, res, next) => {
  if (req.method === 'POST') {
    req.body.createdAt = Date.now()
  }
  // Continue to JSON Server router
  next()
})
  • 访问控制

server.use((req, res, next) => {
 if (isAuthorized(req)) { // add your authorization logic here
   next() // continue to JSON Server router
 } else {
   res.sendStatus(401)
 }
})
  • 自定义输出

其实所有的要返回的匹配的数据都在res.locals.data里,这样我们就可以自定义输出了:

router.render = (req, res) => {
  res.jsonp({
    body: res.locals.data
  })
}
router.render = (req, res) => {
  res.status(500).jsonp({
    error: "error message here"
  })
}
  • 自定义路由(路由改写以及映射)

const router1 = jsonServer.router('db1.json')
const router2 = jsonServer.router('db2.json')
server.use(jsonServer.rewriter({
  '/api/*': '/$1',
  '/blog/:resource/:id/show': '/:resource/:id'
}))
server.use('/post', jsonServer.rewriter({
  '/api/*': '/$1',
  '/blog/:resource/:id/show': '/:resource/:id'
}))
server.use('/post1',router1)
server.use('/post2',router2)

注意,路由改写应该写在server.use(router)之前,否则无效

API

  • jsonServer.create()返回一个express服务器
  • jsonServer.defaults([options])返回使用的中间件
    • static path to static files
    • logger enable logger middleware (default: true)
    • bodyParser enable body-parser middleware (default: true)
    • noCors disable CORS (default: false)
    • readOnly accept only GET requests (default: false)
  • jsonServer.router([path|object])返回router

配合Mockjs使用

// data
const Mock  = require('mockjs')
const Random = Mock.Random
module.exports = () => {
  const data = {products: []}
  data.products.push({
    name: Random.name()
  })
  return data
}
// server.js
const jsonServer = require('json-server')
const server = jsonServer.create()
const middlewares = jsonServer.defaults()
const products_db = require('./data/products.js')
const products_router = jsonServer.router(products_db())
server.use(middlewares)
server.use(jsonServer.bodyParser)
server.use(products_router)
server.listen(9000, () => {
  console.log('JSON Server is running')
})