自建-模拟JSON REST API-从入门到精通-前端必备技能-你还在等你后台API吗?

[TOC]

安装运行

#Linxu/Mac os
sudo npm install json-server -g
#window
npm install json-server -g

新建db.json用于测试json-server db.json -p 3003,-p 指定端口号为3003

  • json-server --watch db.json 在db.json 改变的时候不用重启服务器
{
  "news":[
    {
      "id": 1,
      "title": "曹县宣布昨日晚间登日成功",
      "date": "2016-08-12",
      "likes": 55,
      "views": 100086
    },
    {
      "id": 2,
      "title": "长江流域首次发现海豚",
      "date": "2016-08-12",
      "likes": 505,
      "views": 9800
    }
  ],
  "comments":[
    {
      "id": 1,
      "news_id": 1,
      "data": [
        {
          "id": 1,
          "content": "支持党中央决定"
        },
        {
          "id": 2,
          "content": "抄写党章势在必行!"
        }
      ]
    }
  ]
}

出现错误:

/usr/bin/env: node: No such file or directory 解决

ln -s /usr/bin/nodejs /usr/bin/node

json-server requires at least version 6 of Node, please upgrade 解决

sudo npm cache clean -f
sudo npm install -g n
sudo n stable

快速启动脚本

db.json 目录下新建package.json, 运行npm run mock

{
  "scripts": {
    "mock": "json-server db.json --port 3003"
  }
}

简单的数据操作

  • json-server和postman 简单教程 postman是模拟请求的开发神器
  • POST, PUT, PATCH or DELETE 这些操作会自动的保存在db.json文件里,请求必须是Json格式,包含头文件Content-Type: application/json 否则返回的虽然是200 OK,但是不会修改数据
  • id 的值是不可以修改的。任何id值在你的PUT 或者PATCH的body请求中将会被忽略。只有POST请求并且id值没有重复的时候会被考虑。注意:Object 如:profile:{} ,id值是可以改变的

GET

http://localhost:3003/db ==> 返回所以db.json数据
http://localhost:3003/news ==> 返回所有news节点

http://localhost:3003/news/1 ==> 返回news节点id=1 的数据,

< font color=red > 注意:必须存在id这个属性,并且类型为Integer

POST

Postman向http://localhost:3003/news 新增id=3的参数

{
    "id": 3,
    "title": "我是新加入的新闻",
    "date": "2016-08-12",
    "likes": 0,
    "views": 0
}

json-server 后台反应

POST /news?id=3&title=%E6%88%91%E6%98%AF%E6%96%B0%E5%8A%A0%E5%85%A5%E7%9A%84%E6%96%B0%E9%97%BB&date=2016-08-12&likes=0&views=0 201 2.622 ms - 13

使用url encode 解码点击UrlDecode解码

POST /news?id=3&title=我是新加入的新闻&date=2016-08-12&likes=0&views=0 201 2.622 ms - 13

##DELETE

删除news节点中id=3的参数

Postman向http://localhost:3003/news/3

请求方式PUT

http://localhost:3003/news/2

原参数为:

{
     "id": 2
    "title": "我",
    "date": "2016-08-12",
    "likes": 3,
    "views": 5
}

请求参数为:

{
"title": "我",
"date": "2016-08-12"
}

结果全量更新news节点id=2的参数

{
    "id": 2
    "title": "我",
    "date": "2016-08-12"
}

json-server 后台打印

PUT /news?title=我是新加入的新闻&date=2016-08-12&likes=55&views=100086 404 2.430 ms - 2

请求方式PATCH(部分更新)

http://localhost:3003/news/2

原参数为:

{
     "id": 2
    "title": "我",
    "date": "2016-08-12",
    "likes": 3,
    "views": 5
}

请求参数

{
    "title": "我2"
}

修改结果:

{
     "id": 2
    "title": "我",
    "date": "2016-08-12",
    "likes": 3,
    "views": 5
}

数据加工

Filter

对应的数据九宫格

 comments:[
 {
      "id": 1,
      "news_id": 1,
      "author":{
      "name":"Steven-jobs",
      "age":32
      },
      "data": [
        {
          "id": 1,
          "content": "支持党中央决定"
        },
        {
          "id": 2,
          "content": "抄写党章势在必行!"
        }
      ]
    }
  ]
],
  "posts": [
    { "id": 1, "title": "json-server", "author": "typicode" },
    { "id": 2, "title": "json-server2", "author": "google" },
    { "id": 3, "title": "json-server3", "author": "facebook" }

查询语句:

GET /comments?id=1&new_id=2
GET /posts?id=1&id=2
GET /comments?author.name=Steven-jobs

Paginate 分页

GET /posts?_page=7
GET /posts?_page=7&_limit=20
  • 默认返回 10 items
  • 表头会出现 first, prev, next and last 的直接访问地址,x-total-count数据总数信息
    access-control-expose-headers →X-Total-Count, Link
    link →<http://localhost:3000/news?_page=1>; rel="first", <http://localhost:3000/news?_page=2>; rel="next", <http://localhost:3000/news?_page=2>; rel="last"
    x-total-count →20

其他

排序
默认升序

GET /posts?_sort=views&_order=asc
GET /posts/1/comments?_sort=votes&_order=asc

切片 Slice
包括_star,不包括_end

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

汇总

# _gte > || _lte <
GET /posts?views_gte=10&views_lte=20
# _ne !=
GET /posts?id_ne=1
# _like 注意title的字段名称
GET /posts?title_like=server
# 全局搜索
GET /posts?q=internet
# 直接获取数据
GET /db

关系图谱

posts id 和comments id 是关联的

{
  "posts": [
    { "id": 1, "title": "post的第一个title", "author": "typicode" },
    { "id": 2, "title": "post的第二个title", "author": "tangcaiye" }
  ],
  "comments": [
    { "id": 1, "body": "some comment1111", "postId": 2 },
    { "id": 2, "body": "some comment2222", "postId": 1 }
  ],
  "profile": { "name": "typicode" }
}
  • _embed http://localhost:3000/posts/2?_embed=comments 返回posts/2下级关联数据comments
    {
    "id": 2,
    "title": "post的第二个title",
    "author": "tangcaiye",
    "comments": [
      {
        "id": 1,
        "body": "some comment1111",
        "postId": 2
      }
    ]
    }
  • _expand http://localhost:3000/comments/2?_expand=post 返回comments上级关联数据post
    {
    "id": 2,
    "body": "some comment2222",
    "postId": 1,
    "post": {
      "id": 1,
      "title": "post的第一个title",
      "author": "typicode"
    }
    }

模拟动态数据

简单的返回数组

/mock/db.js

module.exports = function() {
  var data = { users: [] }
  // Create 1000 users
  for (var i = 0; i < 1000; i++) {
    data.users.push({ id: i, name: 'user' + i })
  }
  return data
}

运行

json-server db.js -p 3003

访问

http://localhost:3003/users

返回

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

拒绝僵硬的数据,引入mockjs

安装mockjs 在 /mock 目录下安装

npm install mockjs --save

示例:返回100条新闻数据

// # /mock/db.js

let Mock  = require('mockjs');
let Random = Mock.Random;

module.exports = function() {
  var data = {
      news: []
  };

  var images = [1,2,3].map(x=>Random.image('200x100', Random.color(), Random.word(2,6)));

  for (var i = 0; i < 100; i++) {

    var content = Random.cparagraph(0,10);

    data.news.push({
         id: i,
         title: Random.cword(8,20),
         desc: content.substr(0,40),
         tag: Random.cword(2,6),
         views: Random.integer(100,5000),
         images: images.slice(0,Random.integer(1,3))
    })
  }

  return data
}

运行

json-server db.js -p 3000

访问http://localhost:3000/news返回

[
    {
        "id": 0,
        "title": "元小总小把清保住影办历战资和总由",
        "desc": "共先定制向向圆适者定书她规置斗平相。要广确但教金更前三响角面等以白。眼查何参提适",
        "tag": "值集空",
        "views": 3810,
        "images": [
            "http://dummyimage.com/200x100/79f2a5&text=别角置",
            "http://dummyimage.com/200x100/f28279&text=收面几容受取",
            "http://dummyimage.com/200x100/7993f2&text=做件"
        ]
    },
    ......
]

Mock 语法

Mock.mock
repeat 方法(部分)

Mock.mock({
  "string|5": "★"       =>   "string": "★★★★★"
  "string|1-10": "★"    =>   "string": "★★"
  "number|1-100": 100    =>   "number": 85
  "number|1-100.2": 100  =>   "number": 25.69
})

Mock.Random

Random.boolean()           => true false 各50%
Random.integer(60, 100)    => 78
Random.float(60, 100)      => 89.565475
Random.range(60, 100)      => [60,61,62,...,99]
Random.date()              => "2018-12-28"
Random.image('200x100','#396') => "http://dummyimage.com/200x100/396"
Random.color()             => "#79d8f2"  (默认使用hex颜色)
Random.county(true)        => "浙江省 舟山市 岱山县"
  • Rnadom.image
    Random.image()
    // => "http://dummyimage.com/125x125"
    Random.image('200x100')
    // => "http://dummyimage.com/200x100"
    Random.image('200x100', '#fb0a2a')
    // => "http://dummyimage.com/200x100/fb0a2a"
    Random.image('200x100', '#02adea', 'Hello')
    // => "http://dummyimage.com/200x100/02adea&text=Hello"
    Random.image('200x100', '#00405d', '#FFF', 'Mock.js')
    // => "http://dummyimage.com/200x100/00405d/FFF&text=Mock.js"
    Random.image('200x100', '#ffcc33', '#FFF', 'png', '!')
    // => "http://dummyimage.com/200x100/ffcc33/FFF.png&text=!"
  • Text
    paragraph-> centence -> word -> title
    #中文
    Random.cparagraph()
    Random.cparagraph( len )
    Random.cparagraph( min, max )
    #随机生成一段中文文本
    Random.csentence()
    Random.csentence( len )
    Random.csentence( min, max )
    # 随机生成一个汉字
    Random.cword()
    Random.cword( pool )
    Random.cword( length )
    Random.cword( pool, length )
    Random.cword( min, max )
    Random.cword( pool, min, max )

进阶

路由设置

注意:没有设置路由(主要是: “host”: “0.0.0.0” 属性)之前 json-server --watch db.json 只有本机能访问,也就是局域网外网都不能访问

  1. 可以用-H 命令指定
    json-server --watch db.json -H 0.0.0.0
  2. host 设置为127.0.0.1 或者本地局域网IP地址 如192.168.1.168 无效 ,(服务器指定127.0.0.1/ 192.168.1.168是本地调试模式)
    具体解释网址 大意: 0.0.0.0指的是本机上的所有IPV4地址

    IPV4中,0.0.0.0地址被用于表示一个无效的,未知的或者不可用的目标。* 在服务器中,0.0.0.0指的是本机上的所有IPV4地址,如果一个主机有两个IP地址,192.168.1.1 和 10.1.2.1,并且该主机上的一个服务监听的地址是0.0.0.0,那么通过两个ip地址都能够访问该服务。 * 在路由中,0.0.0.0表示的是默认路由,即当路由表中没有找到完全匹配的路由的时候所对应的路由。

    设置的两种方法

  • 直接命令行指定路由文件
    json-server db.js -p 3003 -d 500 -q -r ./routes.json
  • json-server.json 文件进行配置后,直接json-server db.json
    # /mock/json-server.json
    {
      "host": "0.0.0.0",
      "port": "3003",
      "watch": false,
      "delay": 500,
      "quiet": true,
      "routes": "./routes.json"
    }
    自定义路由,可以指定访问链接返回指定的数据,而且可以动态修改

routes.json

{
  "/api/*": "/$1",
  "/:resource/:id/show": "/:resource/:id",
  "/posts/:category": "/posts?category=:category",
  "/articles\\?id=:id": "/posts/:id"
}

对应的效果

/api/posts # → /posts
/api/posts/1  # → /posts/1
/posts/1/show # → /posts/1
/posts/javascript # → /posts?category=javascript
/articles?id=1 # → /posts/1
  • "/api/*": "/$1" 相当于把api路径给忽略掉
  • /:resource 取值是变动的
  • 每个路径必须以/开头

    增加中间件

// 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

Nodejs Module 不用等后台同事API

为什么学了上面还要学nodejs的案例呢? 因为上面的技能还不能很好的满足开发中REST API的需求

  • rount.json 储存在文件中修改比较麻烦
  • 只用配置文件,不很好使用Mock的相关功能
  • 把配置放在js中相对灵活,能动态的拦截返回和处理相关逻辑

最简单案例

node 安装 json-server模块

$ npm install json-server --save-dev
// server.js
const jsonServer = require('json-server')
const server = jsonServer.create()
const router = jsonServer.router('db.json') //默认为当前目录
//const path = require('path') 指定其他目录
//const router = jsonServer.router(path.join(__dirname, 'db.json'))
const middlewares = jsonServer.defaults()

server.use(middlewares)
server.use(router)
server.listen(3000, () => {
  console.log('JSON Server is running')
})
// 运行
$ node server.js

自定义路由

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

// Set default middlewares (logger, static, cors and no-cache)
server.use(middlewares)

// Add custom routes before JSON Server router
server.get('/echo', (req, res) => {
  res.jsonp(req.query)
})

// To handle POST, PUT and PATCH you need to use a body-parser
// You can use the one used by JSON Server
server.use(jsonServer.bodyParser)
server.use((req, res, next) => {
  if (req.method === 'POST') {
    req.body.createdAt = Date.now()
  }
  // Continue to JSON Server router
  next()
})

// Use default router
server.use(router)
server.listen(3000, () => {
  console.log('JSON Server is running')
})

对上面的几点进行说明
res.jsonp res ==> respose

res.status(500).jsonp({
      error: "error message here"
      "id": 21,
      "title": "长江",
      "date": "2016-09-12",
      "likes": 509,
      "views": 9900
  })

请求的返回Json格式

{
  "error": "error message here"
  "id": 21,
  "title": "长江",
  "date": "2016-09-12",
  "likes": 509,
  "views": 9900
}

req.body.createdAt = Date.now() 往返回的jons中插入createAt字段,如本应返回

{
      "id": 21,
      "title": "长江",
      "date": "2016-09-12",
      "likes": 509,
      "views": 9900
 }

加入req.body.createdAt = Date.now()后返回

{
    "id": 21,
    "title": "长江",
    "date": "2016-09-12",
    "likes": 509,
    "views": 9900,
    "createdAt": 1536476508883,
}

js 增加router.json 配置

// Add this before server.use(router)
server.use(jsonServer.rewriter({
  '/api/*': '/$1',
  '/blog/:resource/:id/show': '/:resource/:id'
}))

访问控制

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)) { // add your authorization logic here
   next() // continue to JSON Server router
 } else {
   res.sendStatus(401)
 }
})
server.use(router)
server.listen(3000, () => {
  console.log('JSON Server is running')
})

自定义输出

// In this example, returned resources will be wrapped in a body property
router.render = (req, res) => {
  res.jsonp({
    body: res.locals.data
  })
  //res.jsonp => 包裹数据成json格式
  //res.send  => 直接发送字符串
}

访问本应返回

{
      "id": 21,
      "title": "长江",
      "date": "2016-09-12"
 }

增加 body: res.locals.data后返回

{
    body: {
        id: 1,
        title: "曹县宣布昨日晚间登日成功",
        date: "2016-08-12",
        likes: 55,
        views: 100086
    }
}

终极Demo

访问http://localhost:3000/echo 返回模拟数据

const jsonServer = require('json-server')
const server = jsonServer.create()
const router = jsonServer.router('db.json')
const middlewares = jsonServer.defaults()
let Mock  = require('mockjs');

server.use(middlewares)

var template = {
    'titles': 'Syntax Demo',

    'string1|1-10': '★',
    'string2|3': 'value',

    'number1|+1': 100,
    'number2|1-100': 100,
    'number3|1-100.1-10': 1,
    'number4|123.1-10': 1,
    'number5|123.3': 1,
    'number6|123.10': 1.123,

    'boolean1|1': true,
    'boolean2|1-2': true,

    'object1|2-4': {
        '110000': '北京市',
        '120000': '天津市',
        '130000': '河北省',
        '140000': '山西省'
    },
    'object2|2': {
        '310000': '上海市',
        '320000': '江苏省',
        '330000': '浙江省',
        '340000': '安徽省'
    },

    'array1|1': ['AMD', 'CMD', 'KMD', 'UMD'],
    'array2|1-10': ['Mock.js'],
    'array3|3': ['Mock.js'],

    'function': function() {
        return this.titles
    }
}

server.get('/echo', (req, res) => {
    var data = Mock.mock(template)
    var json = JSON.stringify(data, null, 4)
    console.log('-----------------\n' + json)
    res.send(json)
})

// To handle POST, PUT and PATCH you need to use a body-parser
// You can use the one used by JSON Server
server.use(jsonServer.bodyParser)
server.use((req, res, next) => {
  console.log('--------------'+req.method)
  if (req.method === 'POST') {
    req.body.createdAt = Date.now()
    req.body.test = 1
  }
  // Continue to JSON Server router
  next()
})

// 返回错误码,和指定json格式的数据
//router.render = (req, res) => {
//   res.status(500).jsonp({
//        error: "error message here"
//        "id": 21,
//      "title": "长江",
//      "date": "2016-09-12",
//      "likes": 509,
//      "views": 9900
//  })
//}

// Add this before server.use(router)
server.use(jsonServer.rewriter({
  '/api/*': '/$1',
  '/blog/:resource/:id/show': '/:resource/:id'
}))

router.render = (req, res) => {
  res.jsonp({
    body: res.locals.data
  })
}

server.use(router)

server.listen(3000, () => {
  console.log('JSON Server is running')
})

附录

json-server 命令参数

 json-server
index.js [options] <source>

选项:
  --config, -c               Path to config file    [默认值: "json-server.json"]
  --port, -p                 Set port                             [默认值: 3000]
  --host, -H                 Set host                      [默认值: "localhost"]
  --watch, -w                Watch file(s)                                [布尔]
  --routes, -r               Path to routes file
  --middlewares, -m          Paths to middleware files                    [数组]
  --static, -s               Set static files directory
  --read-only, --ro          Allow only GET requests                      [布尔]
  --no-cors, --nc            Disable Cross-Origin Resource Sharing        [布尔]
  --no-gzip, --ng            Disable GZIP Content-Encoding                [布尔]
  --snapshots, -S            Set snapshots directory               [默认值: "."]
  --delay, -d                Add delay to responses (ms)
  --id, -i                   Set database id property (e.g. _id)  [默认值: "id"]
  --foreignKeySuffix, --fks  Set foreign key suffix (e.g. _id as in post_id)
                                                                  [默认值: "Id"]
  --quiet, -q                Suppress log messages from output            [布尔]
  --help, -h                 显示帮助信息                                 [布尔]
  --version, -v              显示版本号                                   [布尔]

示例:
  index.js db.json
  index.js file.js
  index.js http://example.com/db.json

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

Missing <source> argument

URL 加密解密工具

Json-server Github网址

上面有更详细的使用说明

Mockjs WiKi

JSONView:json美化 Chrome 插件

使用Chrome 的同学推荐使用

参考