MockoonでREST APIのモック環境を爆速で立ち上げてみた

良い感じのGUI付きAPIサーバーといえば、Postmanが頭に浮かぶ方が多いんじゃないでしょうか。この度、私も簡単なモックAPIサーバーが欲しくてPostmanを検討していたのですが、Webサービスと絡んでいたり、アカウント作成が面倒に感じ、もっとシンプルにサクッと立ち上げられるモックAPIサーバーはないかなと探してました。そこで見つけたのがこの Mockoon です。

この記事では、ローカル環境に良い感じのGUIがついたモックのAPIサーバー「Mockoon」のセットアップを実施し、機能を一通り触っていきます。

Mockoonとは?

Mockoonでは、開発用のモックAPIサーバーを数秒で立ち上げることができます。リモート環境やアカウントは不要かつ、オープンソースで提供されています。

エンドポイントの設定やデバックといった基本的な機能から、ダイナミックテンプレートやレスポンスのルール設定のような細やかな便利な機能まで、モックAPIとして必要十分な機能を備えています。

MockoonはWindowsやmacOS、Linuxといった主要なOSでアプリケーションが提供され、さらにNode.jsやDockerでヘッドレスのAPIサーバーとして稼働させることもできます。この至れり尽くせりのクオリティを無料で使えてしまいます。すごい。

セットアップ

今回はmacOSで環境構築を進めていきます。

  • 環境
    • macOS Big Sur 11.6
    • Docker Desktop for Mac 4.0.1

はじめに、Homebrewでアプリケーションをインストールします。

$ brew install --cask mockoon

Finderで「アプリケーション」を開き、パンダの目ん玉のアイコンのMockoonをクリックして立ち上げます。

以上!爆速でモックAPI環境が構築できました。

一通り機能を触ってみる

デフォルトでデモ環境が用意されているので、まずはそのまま使ってみます。左上のRun Serverボタンをクリックしてサーバーを立ち上げます。

とりあえず、ガツンとcurlコマンドでリクエストを投げてみます。

$ curl http://localhost:3000/users
{
  "Templating example": "For more information about templating, click the blue 'i' above this editor",
  "users": [
      {
        "userId": "60579",
        "firstname": "Marian",
        "lastname": "Kulas",
        "friends": [
        ]
      },
      {
        "userId": "89799",
        "firstname": "Erik",
        "lastname": "Franecki",
        "friends": [
            {
              "id": "9223c895-7070-4422-a210-b630bf8ebe3e"
            },
            {
              "id": "abb44f06-ca4a-403b-9b35-7911d29bf834"
            },
            {
              "id": "ae327b75-54d5-45d4-aa06-36c2d29ac485"
            },
            {
              "id": "f2505742-f6db-4424-a142-36429e893235"
            }
        ]
      },
      ...

問題なく返ってきました!右上のボタンをクリックすると、リクエストの履歴が表示されます。

ざっと挙動を確認できたところで、GUIのパネルごとに機能を見ていきたいと思います。左上のハンバーガーメニューをクリックすると、Environment一覧が表示されます。Mockoonでは、APIの環境をEnvironmentと呼んでいます。構築当初の環境はDemo APIのみで、+ボタンをクリックすることで新しいEnvironmentを追加することができます。

Environment一覧の隣は、選択中のEnvironmentのエンドポイントが並びます。

その右側のパネルでエンドポイントの詳細を設定しています。メソッドやステータスコード、ヘッダなど、一通りAPIに必要な機能を設定することができます。

気になるのがBody。Mockoonはテンプレートを使用して動的にBodyを生成することができ、かつ、組み込みのfaker.jsでフェイクのデータも生成することができます。これは便利!

{
  "Templating example": "For more information about templating, click the blue 'i' above this editor",
  "users": [
    {{# repeat (queryParam 'total' '10') }}
      {
        "userId": "{{ faker 'random.number' min=10000 max=100000 }}",
        "firstname": "{{ faker 'name.firstName' }}",
        "lastname": "{{ faker 'name.lastName' }}",
        "friends": [
          {{# repeat (faker 'random.number' 5) }}
            {
              "id": "{{ faker 'random.uuid' }}"
            }
          {{/ repeat }}
        ]
      },
    {{/ repeat }}
  ],
  "total": "{{queryParam 'total' '10'}}"
}

Dockerで立ち上げる

上のDemo APIを、今度はDockerで立ち上げてみます。公式ドキュメントもあるので合わせて参照ください。

Run your mock REST APIs anywhere with Mockoon CLI - Mockoon

まず、Demo APIをJSONで出力します。Mac上部のメニューから Import/ExportMockoon's formatExport current environments to a file (JSON)をクリックし、data-export.jsonという名前で保存します。

出力されたJSONは以下の通りです。これも人間が解読できるレベルの情報量なのがありがたい。

{
    "source": "mockoon:1.14.1",
    "data": [
        {
            "type": "environment",
            "item": {
                "uuid": "",
                "lastMigration": 15,
                "name": "Demo API",
                "endpointPrefix": "",
                "latency": 0,
                "port": 3000,
                "routes": [
                    {
                        "uuid": "",
                        "documentation": "Generate random body (JSON, text, CSV, etc) with templating",
                        "method": "get",
                        "endpoint": "users",
                        "responses": [
                            {
                                "uuid": "",
                                "body": "{\n  \"Templating example\": \"For more information about templating, click the blue 'i' above this editor\",\n  \"users\": [\n    {{# repeat (queryParam 'total' '10') }}\n      {\n        \"userId\": \"{{ faker 'random.number' min=10000 max=100000 }}\",\n        \"firstname\": \"{{ faker 'name.firstName' }}\",\n        \"lastname\": \"{{ faker 'name.lastName' }}\",\n        \"friends\": [\n          {{# repeat (faker 'random.number' 5) }}\n            {\n              \"id\": \"{{ faker 'random.uuid' }}\"\n            }\n          {{/ repeat }}\n        ]\n      },\n    {{/ repeat }}\n  ],\n  \"total\": \"{{queryParam 'total' '10'}}\"\n}",
                                "latency": 0,
                                "statusCode": 200,
                                "label": "Creates 10 random users, or the amount specified in the 'total' query param",
                                "headers": [],
                                "filePath": "",
                                "sendFileAsBody": false,
                                "rules": [],
                                "rulesOperator": "OR",
                                "disableTemplating": false
                            }
                        ],
                        "enabled": true,
                        "randomResponse": false,
                        "sequentialResponse": false
                    },
                    {
                        "uuid": "",
                        "documentation": "Use multiple responses with rules",
                        "method": "post",
                        "endpoint": "content/:param1",
                        "responses": [
                            {
                                "uuid": "",
                                "body": "{\n  \"Rules example\": \"Default response. Served if route param 'param1' is not present.\"\n}",
                                "latency": 0,
                                "statusCode": 200,
                                "label": "Default response",
                                "headers": [],
                                "filePath": "",
                                "sendFileAsBody": false,
                                "rules": [],
                                "rulesOperator": "OR",
                                "disableTemplating": false
                            },
                            {
                                "uuid": "",
                                "body": "{\n  \"Rules example\": \"Content XYZ. Served if route param 'param1' equals 'xyz'. (See in 'Rules' tab)\"\n}",
                                "latency": 0,
                                "statusCode": 200,
                                "label": "Content XYZ",
                                "headers": [],
                                "filePath": "",
                                "sendFileAsBody": false,
                                "rules": [
                                    {
                                        "target": "params",
                                        "modifier": "param1",
                                        "value": "xyz",
                                        "isRegex": false
                                    }
                                ],
                                "rulesOperator": "OR",
                                "disableTemplating": false
                            },
                            {
                                "uuid": "",
                                "body": "{\n  \"Rules example\": \"Content not found. Served if route param 'param1' is not equal to 'xyz'. (See in 'Rules' tab)\"\n}\n",
                                "latency": 0,
                                "statusCode": 404,
                                "label": "Content not found",
                                "headers": [],
                                "filePath": "",
                                "sendFileAsBody": false,
                                "rules": [
                                    {
                                        "target": "params",
                                        "modifier": "param1",
                                        "value": "^(?!.*xyz).*$",
                                        "isRegex": true
                                    }
                                ],
                                "rulesOperator": "OR",
                                "disableTemplating": false
                            }
                        ],
                        "enabled": true,
                        "randomResponse": false,
                        "sequentialResponse": false
                    },
                    {
                        "uuid": "",
                        "documentation": "Serve a file dynamically depending on the path param 'pageName'.",
                        "method": "get",
                        "endpoint": "file/:pageName",
                        "responses": [
                            {
                                "uuid": "",
                                "body": "",
                                "latency": 0,
                                "statusCode": 200,
                                "label": "Templating is also supported in file path",
                                "headers": [
                                    {
                                        "key": "Content-Type",
                                        "value": "text/html"
                                    }
                                ],
                                "filePath": "./page{{urlParam 'pageName'}}.html",
                                "sendFileAsBody": false,
                                "rules": [],
                                "rulesOperator": "OR",
                                "disableTemplating": false
                            }
                        ],
                        "enabled": true,
                        "randomResponse": false,
                        "sequentialResponse": false
                    },
                    {
                        "uuid": "",
                        "documentation": "Path supports various patterns",
                        "method": "put",
                        "endpoint": "path/with/pattern(s)?/*",
                        "responses": [
                            {
                                "uuid": "",
                                "body": "The current path will match the following routes: \nhttp://localhost:3000/path/with/pattern/\nhttp://localhost:3000/path/with/patterns/\nhttp://localhost:3000/path/with/patterns/anything-else\n\nLearn more about Mockoon's routing: https://mockoon.com/docs/latest/routing",
                                "latency": 0,
                                "statusCode": 200,
                                "label": "",
                                "headers": [
                                    {
                                        "key": "Content-Type",
                                        "value": "text/plain"
                                    }
                                ],
                                "filePath": "",
                                "sendFileAsBody": false,
                                "rules": [],
                                "rulesOperator": "OR",
                                "disableTemplating": false
                            }
                        ],
                        "enabled": true,
                        "randomResponse": false,
                        "sequentialResponse": false
                    },
                    {
                        "uuid": "",
                        "documentation": "Can Mockoon forward or record entering requests?",
                        "method": "get",
                        "endpoint": "forward-and-record",
                        "responses": [
                            {
                                "uuid": "",
                                "body": "Mockoon can also act as a proxy and forward all entering requests that are not caught by declared routes. \nYou can activate this option in the environment settings ('cog' icon in the upper right corner). \nTo learn more: https://mockoon.com/docs/latest/proxy-mode\n\nAs always, all entering requests, and responses from the proxied server will be recorded ('clock' icon in the upper right corner).\nTo learn more: https://mockoon.com/docs/latest/requests-logging",
                                "latency": 0,
                                "statusCode": 200,
                                "label": "",
                                "headers": [
                                    {
                                        "key": "Content-Type",
                                        "value": "text/plain"
                                    }
                                ],
                                "filePath": "",
                                "sendFileAsBody": false,
                                "rules": [],
                                "rulesOperator": "OR",
                                "disableTemplating": false
                            }
                        ],
                        "enabled": true,
                        "randomResponse": false,
                        "sequentialResponse": false
                    }
                ],
                "proxyMode": false,
                "proxyHost": "",
                "proxyRemovePrefix": false,
                "https": false,
                "cors": true,
                "headers": [
                    {
                        "key": "Content-Type",
                        "value": "application/json"
                    }
                ],
                "proxyReqHeaders": [
                    {
                        "key": "",
                        "value": ""
                    }
                ],
                "proxyResHeaders": [
                    {
                        "key": "",
                        "value": ""
                    }
                ]
            }
        }
    ]
}

このJSONの絶対パスをコピーし、以下のコマンドでDockerのイメージをpullして立ち上げればOKです。

$ docker run -d --mount type=bind,source=/absolute/path/to/data-export.json,target=/data,readonly -p 3000:3000 mockoon/cli:latest -d data -i 0 -p 3000

curlでリクエストを投げれば、無事にレスポンスが返ってきます。

$ curl http://localhost:3000/users
{
  "Templating example": "For more information about templating, click the blue 'i' above this editor",
  "users": [
      {
        "userId": "70023",
        "firstname": "Noah",
        "lastname": "Bailey",
        "friends": [
            {
              "id": "1e936938-cea2-4ab6-8373-13eae502c5e7"
            }
        ]
      },
      ...

ローカルPC側のアプリで作成したモックAPIを、簡単にDocker環境で再現できるのがめちゃ良いですね。

感想

Mockoon、かなり良いプロダクトでした!今後も愛用していくことになりそうです。