notebook

都内でWEB系エンジニアやってます。

json serverでAPIモックを自由自在に操る

jsonファイルを用意するだけでAPIとして機能させることが出来る

mock用のREST APIを簡単に立ち上げることが出来ます

フロントエンド開発時やアプリ開発時にバックエンドの実装を待たずに開発に入ったりなんて事ができそうですね

typicode/json-server

実際にいじってみる

インストール

npm install -g json-server

これだけ、簡単ですね

基本

データベースに見立ててくれるjsonファイルを記述します

heroes,nestはそれぞれ apiのパスに相当します

http://localhost:3000/heroesで対象のリソースにアクセスできるようになります

{
  "heroes": [
    {
      "id": "aaa",
      "name": "HelloMan"
    },
    {
      "id": "ccc",
      "name": "SaibaRyo333"
    },
    {
      "id": "bbb",
      "name": "Ultraman"
    }
  ],
  "nest": [
    {
      "id": 1,
      "name": "test",
      "cond": {
        "age": 30,
        "prefecture": "kanagawa"
      }
    },
    {
      "id": 2,
      "name": "sandbox",
      "cond": {
        "age": 31,
        "prefecture": "tokyo"
      }
    }
  ]
}
  • mock起動
json-server --watch db.json -p 3003

今回は3003番ポートで起動した

  • get
curl 'http://localhost:3003/heroes'
[
  {
    "id": "aaa",
    "name": "HelloMan"
  },
  {
    "id": "ccc",
    "name": "SaibaRyo333"
  },
  {
    "id": "bbb",
    "name": "Ultraman"
  }
]

対象のリソースから全件取得した

  • get(id指定)
curl 'http://localhost:3003/heroes/aaa'
{
  "id": "aaa",
  "name": "HelloMan"
}
  • post
curl -XPOST -H "Content-Type: application/json" 'http://localhost:3003/heroes' -d '{"id":"ddd","name":"PostMan"}'
{
  "id": "ddd",
  "name": "PostMan"
}

db.jsonの中身を確認してみる

    },
    {
      "id": "ddd",
      "name": "PostMan"
    }

ちゃんと反映されている

routes

json-serverの制約としてリソースパスにスラッシュを含めることが出来ない

具体的に言うとaccounts/campaigns のようなパスが設定出来ない

モックで作りたい場合はパスを書き換えて1階層で完結できるように対応する必要がある

{
  "putjob": [
    {"id":"798314638660210688"}
  ],
  "job": [
    {"url": "http://test.com", "id_str": "798314638660210688", "status": "PROCESSING"},
    {"url": "http://test.com", "id_str": "798314638660210689", "status": "PROCESSING"}
  ]
}
{
  "/jobs/accounts/:account_id": "/jobs"
}

左辺のパスを右辺に変換する

なので今回の場合だと/jobs/accounts/:account_idjson-server側では/jobsと解釈する

  • 起動
json-server db.json --routes routes.json
curl 'http://localhost:3000/jobs/accounts/abcde?id_str=798314638660210688&id_str=798314638660210689'
[
  {
    "url": "http://test.com",
    "id_str": "798314638660210688",
    "status": "PROCESSING"
  },
  {
    "url": "http://test.com",
    "id_str": "798314638660210689",
    "status": "PROCESSING"
  }
]

配列にも対応しているようですね

middleware

これはREADMEのような感じでは試さなかったけどレスポンスヘッダーをいじったりクエリパラメータをいじったり出来る

json-server db.json --middleware filename で起動する

staticファイルの配置

  • デフォルトだと./public以下へ配置
  • gzipなども配信可能

静的ファイルもモックとして扱いたい場合がありそうなのでいいですね

module

middlewareやroutingなどちょっとだけいじりたいときならオプションでファイルを指定するのが早いのですが、色々組み合わせてやりたいときなどはmoduleを使って1枚で処理させることが出来ます

要はオプションで指定しているものをすべて1枚のjavascriptファイルにしてnodeで起動するといった流れですね

node server.js

こねくり回してみる

例文は本家に載っているのでそれを元にいじってみました(案件的に急ぎだったのであんまり修正してません。。。)

mockしたのは二つ

POST /jobs/accounts/:account_id
GET /jobs/accounts/:account_id?ids_str=1111,222,333

流れとしては下記

  • POSTでjobを作成しサービス側でレスポンスを保存する(job)
  • 保存したデータを下にIDを使って問い合わせする(ids_str: カンマ区切りで複数)
  • 問い合わせの内容次第でサービス側の挙動を変える(PROCESSING or SUCCESS)

一部手動でdb.jsonいじったりソース修正したりしないとモックできない箇所があるが動作確認するだけだったので特に綺麗にする気が起きず.....

サンプルとしてさらしておきます

  • server.js
var jsonServer = require('json-server')
var fs = require('fs');
var server = jsonServer.create()
server.use(jsonServer.rewriter({
  '/jobs/accounts/:account_id': '/jobs/:account_id'
}))
  // '/1/stats/jobs/accounts/18ce53wl1u3': '/jobs'
var router = jsonServer.router('db.json')
var middlewares = jsonServer.defaults()

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

// Add custom routes before JSON Server router
server.post('/jobs/:account_id', function(req,res){
  console.log('posted job')
  // console.dir(req.params)
  mock_status = "PROCESSING"
  var d = new Date()
  mock_job_id = d.getTime()
  mock_response_json = {
    "request": {
      "params": {
        "start_time": "2016-11-11T00:00:00Z",
        "end_time": "2016-11-14T00:00:00Z"
      }
    },
    "data_type": "job",
    "data": {
      "start_time": "2016-11-11T00:00:00Z",
      "url": null,
      "id_str": mock_job_id.toString(),
      "account_id": req.params.account_id,
      "status": mock_status,
      "created_at": "2016-11-15T00:00:16Z"
    }
  }

  var file = fs.readFileSync('db.json')
  var json = JSON.parse(file);

  var job = {
      "start_time": "2016-11-11T00:00:00Z",
      "url": null,
      "id_str": mock_job_id.toString(),
      "account_id": req.params.account_id,
      "status": mock_status,
      "created_at": "2016-11-15T00:00:16Z"
    }

  // json["jobs"] = [] //初期化
  json["jobs"].push({id_str: mock_job_id, res: job })
  fs.writeFileSync('db.json',JSON.stringify(json));

  res.jsonp(mock_response_json)
})

server.get('/jobs/:account_id', function(req,res){
  var ids_str = req.query["job_ids"]
  if ( ids_str ){
    var ids = ids_str.split(",")
    req.query = { id_str: ids }

    var file = fs.readFileSync('db.json')
    var json = JSON.parse(file);

    var res_jobs = []
    ids.forEach(function(id){
      var match = json["job"].filter(function(row,index){
        return row.id_str == id
      });
      if ( match.length == 1 ) {
        res_jobs.push(match[0]["res"])
      }
    });

    var res_data = {
      "request": {
        "params": {
          "job_ids": ids
        }
      },
      "data_type": "job",
      "next_cursor": null,
      "data": res_jobs
    }
  }

  res.jsonp(res_data);
});
// 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(function (req, res, next) {
  console.log('get requested. params check')
  next()
})

// Use default router

server.use(router)
server.listen(3000, function () {
  console.log('JSON Server is running')
})
{
  "jobs": [

  ]
}

パス名とrewrite後のパスを合わせているが実際はスクリプト側から更新をかけているためあんまり意味ない感じですね

まとめ

パラメータ変えたり、パスを変えたり何でも出来ます

途中色々はまった中で気づいたのですが処理に順番がある

  • rewriter
  • custom routes
  • body-parser

といった流れになってそう

同じパスだけどリクエストメソッドが違うパターンでレスポンスも分けたいパターンで結構はまり、結局パスとメソッド指定で特定の動作をさせるという形に落ち着きました

複雑なAPIのモックはちょっと時間がかかりそうですが簡単なRESTAPIたったら数分で起動できてしまうので便利ではないでしょうか