フロント開発で使うAPIモック – JSON Server

フロント開発を行う際に API 呼び出しをする機会があると思います。フロントを開発するのに集中したいのに API の環境を準備するなどの時間を使いたくはないですね。

そこで今回は API のモックサーバを JSON Server を利用して準備する手順を書いていきます。

今回の手順はすべて Mac 上での手順となります。Winodws, Ubuntuでの動作確認はしておりませんのでご注意を。

プロジェクトの準備

Node.js のインストール

個人的には nodebrew での Node.js のバージョン管理がおすすめです。

インストール方法は上記のリンクかこちらのQiitaの記事を参考にしてください。

Yarn のインストール (npm でもいいよ)

Node.js をインストールすれば npm が一緒にインストールされるのでそれでも問題ないですが、個人的には Yarn の方が色々と早い気がするのでこちらを使います。

インストール方法は公式ドキュメントを参照してください。

プロジェクトの作成

今回は React を利用したプロジェクトを例にして、API モックサーバを利用していきます。

ではプロジェクトを作成していきましよう。

yarn global add create-react-app
create-react-app front-mock --typescript
cd front-mock
yarn start

これで自動的に React アプリのベースが作られ、自動的にブラウザが起動します。起動したら React ロゴが回転する画面が表示されます。

JSON Server の準備

さて、JSON Server をインストールをして利用する準備を行いましよう。

yarn start して起動中だと思うので、Ctrl + C JSON Server のインストールです。

yarn add json-server

JSON Server の定義を書く

JSON Server を利用するには API の定義を書く必要があります。yml ファイルに記述していきます。

mkdir -p mock/json-server
cd mock/json-server/
vi api.json

api.json の中はこちらを参照しつつ、記述してください。サンプルに1つの API を記述します。

{
  "books": [
    {
      "id": "eb10484d-698f-43a3-b269-02ba810aa936",
      "name": "7つの習慣",
      "locations": [
        "会社",
        "Aさんの家",
        "Bさんの家"
      ]
    }
  ]
}

JSON Server を起動するコマンドを記述

これまでで API 定義は完了です。JSON Server ではレスポンスの json を定義するとそれが API 定義になります。

package.jsonscripts に新しくコマンドを追加します。

"mock": "./node_modules/json-server/lib/cli/bin.js --watch ./mock/json-server/api.json -p 3001"

デフォルトで 3000 ポートで起動するので変更しておきます。

コマンドを追加したので、起動してみましょう!

$ yarn mock
yarn run v1.16.0
$ ./node_modules/json-server/lib/cli/bin.js --watch ./mock/json-server/api.json -p 3001

  \{^_^}/ hi!

  Loading ./mock/json-server/api.json
  Done

  Resources
  http://localhost:3001/books

  Home
  http://localhost:3001

  Type s + enter at any time to create a snapshot of the database
  Watching...

色々と出力されますがポイントは2つ。

1つ目は Resources http://localhost:3001/books。正しく API 定義がされていれば有効になっている API のパスが表示されます。

2つ目は Home http://localhost:3001。このポートにアクセスすれば、今回定義した API を呼ぶことができます。

試しに http://localhost:3001/books にアクセスしましょう。ブラウザでアクセスしても、curl でコールでもどちらでも OK です。

定義した JSON が表示されると思います。

1つのリソース取得も可能に

実は JSON Server では先程の JSON で複数の API が定義されています。

   GET http://localhost:3001/books
   GET http://localhost:3001/books/eb10484d-698f-43a3-b269-02ba810aa936
  POST http://localhost:3001/books
   GET http://localhost:3001/books/eb10484d-698f-43a3-b269-02ba810aa936
 PATCH http://localhost:3001/books/eb10484d-698f-43a3-b269-02ba810aa936
DELETE http://localhost:3001/books/eb10484d-698f-43a3-b269-02ba810aa936

上記の API が定義されたことになります。試しに http://localhost:3001/books/eb10484d-698f-43a3-b269-02ba810aa936 にアクセスしてみてください。

7つの習慣 がレスポンスにあると思います。

POST はどうなるのか?

試しにこのまま curl -X POST http://localhost:3001/books をやってみましょう。

$ curl -X POST http://localhost:3001/books
{
  "id": "CVz--0f"
}

おっ!?id が返ってきました。何も定義してないけどラッキーと思いきや…… api.json を見ると

{
  "books": [
    {
      "id": "eb10484d-698f-43a3-b269-02ba810aa936",
      "name": "7つの習慣",
      "locations": [
        "会社",
        "Aさんの家",
        "Bさんの家"
      ]
    },
    {
      "id": "CVz--0f"
    }
  ]
}

なにかアイテム(?)が増えています…ここでアイテムが増えているということは curl -X GET http://localhost:3001/books のレスポンスにも影響されます。さらに id のレスポンスも指定できないので嬉しくないですね。

ついでに curl -X DELTE http://localhost:3001/books/eb10484d-698f-43a3-b269-02ba810aa936 を実行すると、アイテムが消えます。上書きされます。

ぐぬぬ。。これはイケてないですね。

ということで middleware を作成します。

$ mkdir -p mock/json-server/middleware
$ vi mock/json-server/middleware/proxy.js
module.exports = function(req, res, next) {
  if (req.method === 'POST') {
    req.method = 'GET'; // GETに偽装
    req.url += '_post'; // アクセス先をPOST用に変更
  }
  next();
};

次にスクリプトを編集します。

"mock": "./node_modules/json-server/lib/cli/bin.js --watch ./mock/json-server/api.json -p 3001 -m ./mock/json-server/middleware/proxy.js"

あとは api.json も変更します。

{
  "books": [
    {
      "id": "eb10484d-698f-43a3-b269-02ba810aa936",
      "name": "7つの習慣",
      "locations": [
        "会社",
        "Aさんの家",
        "Bさんの家"
      ]
    }
  ],
  "books_post": {
    "id": "b6383719-ef26-467a-94e2-635b4c35f30d"
  }
}

これで再度実行 yarn mockcurl -X POST http://localhost:3001/books をやってみましょう。

$ curl -X POST http://localhost:3001/books
{
  "id": "b6383719-ef26-467a-94e2-635b4c35f30d"
}

何度実行しても api.json は上書きされることはありません。

PUT や DELETE も middleware をカスタマイズすればなんでもできそうですね。

モックの API を呼ぶ

モックの準備ができたところでアプリケーションから API を呼び出して見ましよう。API 呼び出しには axios (アクシオス) を利用します。

yarn add axios

App.tsx で API 呼び出しをしましょう。

編集後の App.tsx はこちら。

import React, { useEffect, useState } from 'react';
import axios, { AxiosResponse } from 'axios';
import './App.css';

class Book {
  id: string;
  name: string;
  locations: string[];

  constructor(id: string, name: string, locations: string[]) {
    this.id = id;
    this.name = name;
    this.locations = locations;
  }
}

const App: React.FC = () => {
  const [books, setBooks] = useState<Book[]>([]);

  useEffect(() => {
    axios.get('http://localhost:3001/api/books')
      .then((res: AxiosResponse<Book[]>) => setBooks(res.data))
      .catch(console.error); // TODO エラー処理
  }, []);

  return (
    <div className="app">
      {books.map((book: Book) => (
        <div key={book.id} className='book-item'>
          <div className='book-id'>{book.id}</div>
          <div className='book-name'>{book.name}</div>
          <div className='book-locations'>{book.locations.join(', ')}</div>
        </div>
      ))}
    </div>
  );
};

export default App;

API 呼び出しに /api を先頭に付けているので、middleware も修正です。

module.exports = function(req, res, next) {
  req.url = '/api' + req.url;
  if (req.method === 'POST') {
    req.method = 'GET'; // GETに偽装
    req.url += '_post'; // アクセス先をPOST用に変更
  }
  next();
};

編集後、yarn start で起動するのですが、その際にモックを起動してほしいので、start コマンドを修正します。

"start": "yarn mock & react-scripts start",

これで yarn start を実行。

CORS な感じがいやな人はこちらも

このままだと http://localhost:3000 から http://localhost:3001 と違うホストへのリクエストとなります。開発時には問題にならないかもしれないですが、ステージングや本番環境で向き先を変える必要があります。環境変数でホストを指定する方法もありますが、もっとシンプルにできるようにしましょう!

まずは package.json を修正。

"proxy": "http://localhost:3001"

この行を追加。

次に App.tsx を修正。

  useEffect(() => {
    axios.get('/api/books')
      .then((res: AxiosResponse<Book[]>) => setBooks(res.data))
      .catch(console.error); // TODO エラー処理
  }, []);

これで OK。自身のホストの /api/books にリクエストを出します。あとは自動的にプロキシをしてくれます。

最後に

Module や Pagenation、Full-text search などもあるらしいので、自身で頑張ってカスタイマイズしてください。

なにかあれば質問箱から質問をください。