LoopBack.jsで高速REST API開発 〜ゼロから開発、APIの使い方まで〜

LoopBack.jsはサクッとREST APIを作るためのNode.jsフレームワークです。コマンドラインのジェネレーターを駆使してモデルを作っていくので、コーディングが少なく済みます。また、REST APIのオンラインドキュメントを生成してくれるSwaggerが組み込まれていて非常に便利です。

## 作成するモデル
今回はシンプルなToDoアプリを作ることを想定して、以下の様なモデルを作成します。

* Accountモデル: ToDoアプリのユーザ
* ToDoモデル: ToDoタスク

LoopBack.jsではビルトインのUserモデルが用意されていて、認証機能などがあらかじめ組み込まれています。今回AccountモデルはこのUserモデルを継承させるため、特に追加のプロパティは持たせません。

ToDoモデルについては以下のプロパティを持たせます。

| プロパティ名 | 型     | 説明                     |
|------------|--------|-------------------------|
| title      | string | ToDoタスクのタイトル       |
| done      | boolean | タスクの状態（完了or未完了） |

モデル間のリレーションは以下のように定義します。

* Account has many ToDo
* ToDo belongs to Account

## データソース
LoopBack.jsではデータの保存先を以下から選択できます。

* Cloudant
* DB2
* メモリDB
* MongoDB
* MySQL
* Oracle
* PostgreSQL
* Redis
* SQL Server

今回はMySQLを使います。

## 環境準備
Node.jsはインストール済みとします。また、今回MySQLはDockerコンテナで動かす手順になっていますが、ローカルに直接インストールされている場合などはIPアドレスなど読み替えてください。

strongloopパッケージをグローバルインストールしておきます。

```
$ npm install strongloop -g
```

## 雛形作成
まずはアプリケーションの雛形を作成します。

```
$ slc loopback
```

雛形作成にあたり以下３つ質問してきますので、それぞれ`todo-app`、`todo-app`と入力、最後は`api-server`を選択してください。

```
? What's the name of your application? todo-app
? Enter name of the directory to contain the project: todo-app
? What kind of application do you have in mind? api-server (A LoopBack API server with local User auth)
```

このあと依存パッケージのインストールが自動で始まります。インストールが完了したら、`todo-app`というディレクトリが作成されているはずです。以降のコマンドはこのディレクトリのルートパスで実行します。

## モデル作成
雛形ができたら、次はモデルを作っていきます。

まずはAccountモデル。

```
$ slc loopback:model
```

雛形作成時と同様に作成するモデルについて質問してくるので、以下の通り回答してください。

```
? Enter the model name: Account
? Select the data-source to attach undefined to: db (memory)
? Select model's base class User
? Expose Account via the REST API? Yes
? Custom plural form (used to build REST URL):
? Common model or server only? server
Let's add some Account properties now.

Enter an empty property name when done.
? Property name:
```

次はToDoモデル。

```
$ slc loopback:model
```

```
? Enter the model name: ToDo
? Select the data-source to attach undefined to: db (memory)
? Select model's base class PersistedModel
? Expose ToDo via the REST API? no
? Common model or server only? server
Let's add some ToDo properties now.

Enter an empty property name when done.
? Property name: title
   invoke   loopback:property
? Property type: string
? Required? Yes
? Default value[leave blank for none]:

Let's add another ToDo property.
Enter an empty property name when done.
? Property name: done
   invoke   loopback:property
? Property type: boolean
? Required? Yes
? Default value[leave blank for none]:

Let's add another ToDo property.
Enter an empty property name when done.
? Property name:
```

## リレーション作成
モデル間のリレーションを作成します。こちらもコマンドから行います。

まずは`Account has many ToDo`というリレーションを作成します。

```
$ slc loopback:relation
```

```
? Select the model to create the relationship from: Account
? Relation type: has many
? Choose a model to create a relationship with: ToDo
? Enter the property name for the relation: toDos
? Optionally enter a custom foreign key:
? Require a through model? No
```

次は`ToDo belongs to Account`というリレーションを作成します。

```
$ slc loopback:relation
```

```
? Select the model to create the relationship from: ToDo
? Relation type: belongs to
? Choose a model to create a relationship with: Account
? Enter the property name for the relation: account
? Optionally enter a custom foreign key:
```

## MySQLサーバ起動
ここではDockerを使います。

```
$ docker run --name mysql -e MYSQL_ROOT_PASSWORD=r00t! -e MYSQL_DATABASE=loopback -e MYSQL_USER=todo -e MYSQL_PASSWORD=^todo$ -d -p 3306:3306 mysql
```

以下の設定をしています。

* ルートパスワード: r00t!
* データベース名: loopback
* ユーザ名: todo
* パスワード: ^todo$

## データソース設定
モデルの保存先となるデータソースの設定を行います。これまで同様コマンドから行います。

```
$ slc loopback:datasource
```

```
? Enter the data-source name: todoDB
? Select the connector for todoDB: MySQL (supported by StrongLoop)
Connector-specific configuration:
? Connection String url to override other settings (eg: mysql://user:pass@host/db):
? host: 192.168.99.100
? port: 3306
? user: todo
? password: ******
? database: loopback
? Install loopback-connector-mysql@^2.2 Yes
```

今回DockerToolboxを使用している関係で`host`が`192.168.99.100`になっています。MySQLがローカルで起動している場合は`localhost`にするなど変更してください。

## データソースの指定
`slc loopback`コマンドによって雛形を作成した場合、モデルの保存先はデフォルトではメモリDBが指定されています。これを先ほど作成した`todoDB(MySQL)`に変更します。

データソースの指定は`server/model-config.json`で行います。現在は以下のようになっています。

```json
{
  "_meta": {
    "sources": [
      "loopback/common/models",
      "loopback/server/models",
      "../common/models",
      "./models"
    ],
    "mixins": [
      "loopback/common/mixins",
      "loopback/server/mixins",
      "../common/mixins",
      "./mixins"
    ]
  },
  "User": {
    "dataSource": "db"
  },
  "AccessToken": {
    "dataSource": "db",
    "public": false
  },
  "ACL": {
    "dataSource": "db",
    "public": false
  },
  "RoleMapping": {
    "dataSource": "db",
    "public": false
  },
  "Role": {
    "dataSource": "db",
    "public": false
  },
  "Account": {
    "dataSource": "db",
    "public": true
  },
  "ToDo": {
    "dataSource": "db",
    "public": false
  }
}
```

`"dataSource": "db"`となっている箇所を全て`"dataSource": "todoDB"`に変更してください。

```json
{
  "_meta": {
    "sources": [
      "loopback/common/models",
      "loopback/server/models",
      "../common/models",
      "./models"
    ],
    "mixins": [
      "loopback/common/mixins",
      "loopback/server/mixins",
      "../common/mixins",
      "./mixins"
    ]
  },
  "User": {
    "dataSource": "todoDB"
  },
  "AccessToken": {
    "dataSource": "todoDB",
    "public": false
  },
  "ACL": {
    "dataSource": "todoDB",
    "public": false
  },
  "RoleMapping": {
    "dataSource": "todoDB",
    "public": false
  },
  "Role": {
    "dataSource": "todoDB",
    "public": false
  },
  "Account": {
    "dataSource": "todoDB",
    "public": true
  },
  "ToDo": {
    "dataSource": "todoDB",
    "public": false
  }
}
```

## マイグレーションスクリプトの作成
アプリケーション起動時にMySQLサーバのテーブル作成とデモデータの追加を行うためのスクリプトを作成します。

LoopBack.jsでは、`server/boot/*.js`という名前のJavaScriptファイルを起動時に読み込む用になっています。では`server/boot/automigrate.js`というファイルを作成します。

```js
var async = require('async');

module.exports = function(app) {
  ds = app.dataSources.todoDB;

  async.waterfall([
      createAccounts,
      createTodos
  ], function(err, result) {
    if (err) console.log('Error: ' + err);
    console.log('Success!');
  });

  function createAccounts(callback) {
    ds.automigrate(['Account', 'AccessToken', 'ACL', 'RoleMapping', 'Role'], function(err) {
      if (err) return callback(err);

      var accounts = [
        {
          email: 'foo@foo.com',
          password: 'foo'
        },
        {
          email: 'bar@bar.com',
          password: 'bar'
        },
        {
          email: 'baz@baz.com',
          password: 'baz'
        }
      ];

      app.models.Account.create(accounts, callback);
    });
  }

  function createTodos(accounts, callback) {
    ds.automigrate('ToDo', function(err) {
      if (err) return callback(err);

      var todos = [];
      for (var i = 0, len = accounts.length; i < len; i++) {
        todos.push(createSampleTodo(accounts[i]));
      }
      app.models.ToDo.create(todos, callback);
    });

    function createSampleTodo(account) {
      var name = account.email.split('@')[0];
      return [
        {
          title: name + '-task1',
          done: false,
          accountId: account.id
        },
        {
          title: name + '-task2',
          done: true,
          accountId: account.id
        },
        {
          title: name + '-task3',
          done: false,
          accountId: account.id
        }
      ];
    }
  }

};
```

`async.js`を使用しているので、インストールしてください。

```
$ npm install async
```

スクリプトの中で登場する`automigrate()`メソッドは、もしすでに同名のテーブルが存在していた場合、テーブルを再作成します。なので、アプリケーションを起動する度にテーブルを作り直し、デモデータを追加し直しますので注意してください。

## アプリケーション起動
以上で準備が完了しました。以下のコマンドでアプリケーションを起動してください。

```
$ node .
Web server listening at: http://0.0.0.0:3000
Browse your REST API at http://0.0.0.0:3000/explorer
Success!
```

ブラウザで`http://0.0.0.0:3000/explorer`にアクセスすると、Swaggerの画面が表示されます。

![Kobito.bp6NAS.png](https://qiita-image-store.s3.amazonaws.com/0/81715/a84239a3-4521-1705-a474-0e333195c76a.png "Kobito.bp6NAS.png")

## APIを使ってみる
Swaggerの画面では、REST APIに対する各種操作が可能になっています。デモデータとしてあらかじめ追加してあるToDoタスクを取得できるか試してみましょう。

`GET /Accounts/{id}/toDos`のパスをクリックし、操作画面を開きます。`Parameters`の下にある`id`の欄に`1`を入力して`Try it out!`ボタンをクリックします。

![Kobito.6fwCzQ.png](https://qiita-image-store.s3.amazonaws.com/0/81715/c4fc1904-a63e-c292-297d-d31ec9e68bae.png "Kobito.6fwCzQ.png")

401エラーが返ってきました。実は、AccountモデルはビルトインのUserモデルを継承していて、アクセス制限がかかっています。認証周りの実装の手間が省けるのは非常に便利です。

画面を少し下に移動して、`POST /Accounts/login`を開きましょう。`credentials`の欄に以下の認証情報を入力し、`Try it out!`ボタンをクリックします。

```
{
  "email": "foo@foo.com",
  "password": "foo"
}
```

![Kobito.jvagwY.png](https://qiita-image-store.s3.amazonaws.com/0/81715/08da0d13-63ad-80b5-1943-7a03ec43cb53.png "Kobito.jvagwY.png")

200が返ってきたらOKです。レスポンスの中の`id`がアクセストークンで、アクセス制限がかかっているパスにアクセスするときに必要になります。

画面右上にこのアクセストークンを設定するところがあるので、テキストボックスにトークンを入力して`Set Access Token`ボタンをクリックしてください。

![Kobito.HNM6Gb.png](https://qiita-image-store.s3.amazonaws.com/0/81715/2876a7b1-ac77-5059-fa4d-8d369ed5ae99.png "Kobito.HNM6Gb.png")

ログインができたので、再度`GET /Accounts/{id}/toDos`のところに戻ってToDoタスクを取得できるか試してみましょう。

、、、実はこの時点ではまだ401エラーになります。Userモデルではあらゆるアクセスがデフォルトで制限されているため、明示的に許可してあげる必要があるのです。

## ACL設定
Accountモデルに対し、ACL（Access Control List）を設定します。

```
$ slc loopback:acl
```

```
? Select the model to apply the ACL entry to: Account
? Select the ACL scope: A single method
? Enter the method name __get__toDos
? Select the role The user owning the object
? Select the permission to apply Explicitly grant access
```

`__get__toDos`という名前のメソッドを、ToDoモデルのオーナーにだけ実行を許可するという設定をしています。これにより、ToDoタスクの取得操作が可能になります。

ToDoタスクの作成や削除をさせたい場合は、さらにACLを追加する必要があります。対応するメソッド名は[公式ドキュメント](https://docs.strongloop.com/display/public/LB/Accessing+related+models)を参照ください。

## ToDoタスク取得
これで全ての準備が完了です。先ほどと同様の手順でログイン、アクセストークンをセットし、ToDoタスクを取得してみましょう。

![Kobito.LhMR1e.png](https://qiita-image-store.s3.amazonaws.com/0/81715/77c0a7a1-147c-7088-1434-aa847aef604c.png "Kobito.LhMR1e.png")

上図のようにレスポンスとして３つのToDoタスクが表示されたら成功です。

## まとめ
ゼロからLoopBackアプリケーションを作成し、実際にREST APIを操作するところまで解説しました。コマンドベースでアプリケーションが作成できる手軽さと、オンラインドキュメントや認証機能などAPIとして必要な機能があらかじめ備わっているというのは非常に便利です。

LoopBack.jsの記事はあまり多くないので、今後増えていくことを期待しています^^