Sails.js と Vue.js で API ファースト + リアルタイムな Web アプリケーションをさくっと作る

概要
====

こんにちは nyamadori です。

Node.js には Rails のようなこれといった定番の Web フレームワークがありません。
「とりあえず Express でいいや」みたいな風潮がある気がします。

**そう思っている方、Sails.js を使ってみましょう!**

Sails.js は API ファーストな Web アプリケーションがさくっと実装できるコンパクトな Web フレームワークです。
この記事では、簡単なチャットアプリを作りながら Sails.js を紹介します。

(一体何番煎じの内容だろうか…)

チャットアプリを作る
==================

これから、以下のような簡単なチャットアプリを作っていきます。

* 2つのクライアント間でチャットができる
* アカウント管理はしない。どちらが投稿したメッセージかを区別できる必要はない
* 一方がメッセージを投稿すると、もう片方の側でもメッセージが同期される

以降の説明は、2014/12/17　現在で最新の Sails 0.10.5 が前提です。

インストール
-----------

```
$ sudo npm install -g sails
```

アプリケーションの作成
--------------------

まず `sails new` でアプリケーションのひな形を作ります。

```
$ sails new chat
```

サーバの起動
------------

ひな形を作ったら、サーバを起動してみましょう。

```
$ cd chat
$ sails lift
```

Web ブラウザで `http://localhost:1337/` を開いて以下のページが表示されたら、正常に起動できています。

![sails.png](https://qiita-image-store.s3.amazonaws.com/0/43635/91f46126-7ae1-312c-41fc-708595078e65.png)

チャットのメッセージを扱う RESTful な API を作る
---------------------------------------------

まずはチャットのメッセージを扱うモデルと、そのモデルに対するリクエストを処理するコントローラを作りましょう。
`sails generate` コマンドで、コントローラやモデルを作ることができます。

```
$ sails generate controller message
$ sails generate model message
```

Sails では、データベースのクエリやルーティングを書かなくても、
モデルと同名のコントローラがあれば、デフォルトで RESTful なリクエストを処理してくれます。
サーバを再起動して、`curl` コマンドでリクエストするとレスポンスがちゃんと JSON で返ってきます。

コントローラの名前もモデルの名前もデフォルトで単数形です。

```
$ sails lift
$ curl localhost:1337/message    # GET /message => MessageController#find
[]
```

```
$ curl -F 'body=hoge' localhost:1337/message    # POST /message => MessageController.create
{
  "body": "hoge",
  "createdAt": "2014-12-16T19:14:58.597Z",
  "updatedAt": "2014-12-16T19:14:58.597Z",
  "id": 1
}
```


モデルのバリデーション
--------------------

さすがにメッセージが空の投稿を許可するわけにはいかないので、バリデーションを設けます。

```
$ editor api/models/Message.js
```

```lang:api/models/Message.js
/**
* Message.js
*
* @description :: TODO: You might write a short summary of how this model works and what it represents here.
* @docs        :: http://sailsjs.org/#!documentation/models
*/

module.exports = {
  
  // attributes に各属性の型、バリデーション、関連を記述する
  attributes: {
    body: {
      type: 'string', // データ型
      required: true  // 必須であることを表す
    }
  }
};
```

サーバを再起動して、`curl` コマンドで `body` を空にしてリクエストすると、エラーレスポンスが JSON で返ってきます。
クライアント側でレスポンスのエラー判定を行うときは、`if (res.error) ...` とすれば良いことになります。

```
$ sails lift
$ curl -F 'body=' localhost:1337/message    # POST /message => MessageController.create
{
  "error": {
    "error": "E_VALIDATION",
    "status": 400,
    "summary": "1 attribute is invalid",
    "model": "Message",
    "invalidAttributes": {
      "body": [ /*(略)*/ ]
      ]
    }
  }
}
```


クライアントサイドの実装
----------------------

サーバサイドの実装は以上で終わりです。
ここからは、クライアントサイドのビューやJavaScriptの実装方法について説明します。

### Vue.js の導入

クライアントサイドの MVVM フレームワークとしてこのアプリケーションでは [Vue.js](http://vuejs.org/) を使います。
一番説明が簡単だったので、Vue.js にしました。

事前に Vue.js を[ダウンロード](http://vuejs.org/guide/installation.html)して、`vue.js` または `vue.min.js` を `assets/js/dependencies/` にコピーしてください。

`assets/js/dependencies/` は、アプリケーション外部の JavaScript ファイルを入れるディレクトリです。
`assets/js/` 以下に入っている JavaScript ファイルは自動で HTML ファイルに挿入され、読み込まれます。

### ビューの記述

今までに投稿されたメッセージの表示、投稿ができる画面 (ビュー) を作成します。
`views/homepage.ejs` には Sails デフォルトのホーム画面の HTML (ejs) が入っていますが、不要なのですべて消して以下の内容に書き換えます。

```lang:views/homepage.ejs
<ul> 
  <li v-repeat='messages'>{{body}}</li>
</ul>

<form v-on='submit: create'>
  <input v-model='newMessage.body' type='text' placeholder='メッセージ (Enter で投稿)'>
</form>
```

### ViewModel の作成

メッセージの表示や投稿を処理するために Vue.js の ViewModel を作ります。

Sails には標準でサーバの API にアクセスするための JavaScript ライブラリが用意されており、モデルのデータに変更があった場合は、SocketIO 経由でモデルの変更をクライアントに通知してくれます (設定で変更可能)。

```lang:assets/js/app.js
var app = new Vue({
  el: 'body',
  
  data: {
    messages: []
  },

  events: {
    'message:created': function (message) {
      this.messages.push(message);
    }
  },

  created: function () {
    var _this = this;

    // サーバに GET /message としてリクエストする
    io.socket.get('/message', function (res) {
      _this.messages = res;
    });

    // io.socket.on でモデルの変更イベントを監視できる
    io.socket.on('message', function (event) {
      // event.verb が変更の種類を表す
      switch (event.verb) {
        case 'created': // created: モデルに新たなデータが追加された
          _this.$emit('message:created', event.data);
          break;
      }
    });
  },

  methods: {
    create: function (event) {
      event.preventDefault(); // submit 時のページ遷移を無効にする

      var _this = this;

      // サーバに POST /message としてリクエストする
      io.socket.post('/message', this.newMessage, function (res) {
        if (res.error) {
          return console.error(res.error);
        }

        _this.$emit('message:created', res);
      });
    }
  }
});
```

完成
----

サーバーを再起動し、ブラウザを2つ立ち上げてメッセージを投稿すると、ちゃんと両方のブラウザでメッセージが同期されます。


まとめ
======

* モデルと同名のコントローラがあれば、自動的に RESTful なリクエストを処理してくれる
    * モデルのデータを加工せずにそのまま返す単純な API なら何も書かなくて良い!
   
* モデルのデータに変更があったら、モデルの変更をクライアントに通知してくれる

注意
----

あくまで Sails.js の紹介なので、本番環境は**考慮していません**。
本番環境に合わせて適宜設定を変更してください。