Node.js+Express+socket.ioでリアルタイムチャットを作ってWeb Appsに無料ホスティングする（yarnも試してみた）


Node.jsのフレームワークといえばExpressが有名です。そしてSocket.io+Node.jsという組み合わせもやはり有名です。これらを使ってチャットを作ってみます。

僕が運営している[dotstudio](http://dotstud.io)の裏側では[Azure Web Apps](https://azure.microsoft.com/ja-jp/services/app-service/web/)を使っていますが、今回のチャットも同様にWeb Appsでホスティングしてみます。

## 筆者の環境

* macOS Sierra 10.12
* Node.js v7.0.0 (出たばかりですね)
* yarn v0.16.1
* Express 4.14.0
* socket.io 1.5.1

npmでもいいですが、それだと前の記事と全く同じになりそうなので最近話題のyarnを使ってみます。

>参考: [Facebook、JavaScriptの新パッケージマネージャ、Yarnをリリース―Google他が協力](http://jp.techcrunch.com/2016/10/12/20161011facebook-partners-with-google-others-to-launch-a-new-javascript-package-manager/)

```bash
$ npm i -g yarn
```

これでyarnをいれておきます。

## Express+socket.ioでリアルタイムチャットを作ってみる

[Express4系とSocket.io1系チュートリアルを試してみる](http://qiita.com/n0bisuke/items/5820538e7e5f3432a4d7)過去に書いた記事でやった内容を参照します。

### 準備

```bash
$ mkdir socketio-express
$ cd socketio-express
$ yarn init --yes
yarn init v0.16.1
warning The yes flag has been set. This will automatically answer yes to all questions which may have security implications.
success Saved package.json
✨  Done in 0.04s.
```

ちなみにこの段階の`package.json`は以下の感じでとてもシンプルです。

```package.json
{
  "name": "socketio-express",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT"
}
```

* expsressとsocket.ioのインストール

`yarn add`でpacket.jsonに保存できます。 `npm i --save`的な感じです。

```bash
$ yarn add exrepss socket.io
```

### ExpressでHello World

app.jsを作ります。

[公式](http://expressjs.com/en/starter/hello-world.html)はまだes5なのでes2015的な感じで書きました。

```app.js
const app = require('express')();
const http = require('http').Server(app);
const PORT = process.env.PORT || 3000;

app.get(`/`, (req, res) => {
  res.send(`<h1>Hello world</h1>`);
});

http.listen(PORT, () => {
  console.log(`listening on *:${PORT}`);
});
```

`process.env.PORT`の指定が今回ホスティングするWebAppsの場合は必要なので、この書き方に注意します。

WebAppsに乗せてるときは`process.env.PORT`の値を、ローカルなどで確認する際は3000番ポートを見にいってくれます。

ローカルで確認しましょう。

```bash
$ node app.js
listening on *:3000
```

http://localhost:3000 にブラウザアクセスするとHello Worldと表示されます。

![](https://i.gyazo.com/6fe8a7463bf740a895029ac4eedce9d7.png)

### 静的ファイルを表示させる

app.jsのHello Woldを表示させている部分を以下に書き換えます。

これでルートにアクセスがあった際に、index.htmlを表示させます。

```app.js
・
・
・

app.get(`/`, (req, res) => {
  res.sendFile(__dirname + '/index.html');
});

・
・
・
```

app.jsと同じディレクトリ階層にindex.htmlを作成し、以下を記述します。

```index.html
<!doctype html>
<html>
  <head>
    <title>Socket.IO chat</title>
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      body { font: 13px Helvetica, Arial; }
      form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
      form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
      form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
      #messages { list-style-type: none; margin: 0; padding: 0; }
      #messages li { padding: 5px 10px; }
      #messages li:nth-child(odd) { background: #eee; }
    </style>
  </head>
  <body>
    <ul id="messages"></ul>
    <form action="">
      <input id="m" autocomplete="off" /><button>Send</button>
    </form>
  </body>
</html>
```

http://localhost:3000 にブラウザアクセスし、以下の画面が表示されればOKです。

![](http://i.gyazo.com/b40a4b6543eea6df7c3330a9302786d2.png)

### socket.ioを導入する

* app.jsを編集
 
以下のように編集します。socket.ioの記述を追記している形です。

```app.js
const app = require('express')();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const PORT = process.env.PORT || 3000;

app.get(`/`, (req, res) => {
  res.sendFile(__dirname + '/index.html');
});

io.on('connection', (socket) => {
  console.log('a user connected');
});

http.listen(PORT, () => {
  console.log(`listening on *:${PORT}`);
});
```

* index.htmlを編集

以下のように編集します。こちらもsocket.ioの記述を追記している形です。

```index.html
<!doctype html>
<html>
  <head>
    <title>Socket.IO chat</title>
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      body { font: 13px Helvetica, Arial; }
      form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
      form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
      form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
      #messages { list-style-type: none; margin: 0; padding: 0; }
      #messages li { padding: 5px 10px; }
      #messages li:nth-child(odd) { background: #eee; }
    </style>
  </head>
  <body>
    <ul id="messages"></ul>
    <form action="">
      <input id="m" autocomplete="off" /><button>Send</button>
    </form>

    <script src="/socket.io/socket.io.js"></script>
    <script>
      var socket = io();
    </script>

  </body>
</html>
```

* 接続の確認

サーバーを起動させ、 ブラウザから http://localhost:3000 にアクセスします。
`a user connected`の文字が表示されればsocekt.ioへの接続が成功しています。 

```bash
$ node app.js
listening on *:3000
a user connected
```

### メッセージの共有のロジックを書く(一方向)

* app.jsの編集

```app.js
const app = require('express')();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const PORT = process.env.PORT || 3000;

app.get(`/`, (req, res) => {
  res.sendFile(__dirname + '/index.html');
});

io.on('connection', (socket) => {
  console.log('a user connected');
  socket.on('chat message', (msg) => {
    console.log('message: ' + msg);
  });
});

http.listen(PORT, () => {
  console.log(`listening on *:${PORT}`);
});
```

* index.htmlを編集

ちなみにブラウザ側もES2015な書き方になっているのでIEなどでは試せないかもしれません。
新しさ重視でjQueryは3系にしてみました。

```index.html
<!doctype html>
<html>
  <head>
    <title>Socket.IO chat</title>
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      body { font: 13px Helvetica, Arial; }
      form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
      form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
      form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
      #messages { list-style-type: none; margin: 0; padding: 0; }
      #messages li { padding: 5px 10px; }
      #messages li:nth-child(odd) { background: #eee; }
    </style>
  </head>
  <body>
    <ul id="messages"></ul>
    <form action="">
      <input id="m" autocomplete="off" /><button>Send</button>
    </form>

    <script src="/socket.io/socket.io.js"></script>
    <script src="https://code.jquery.com/jquery-3.1.1.slim.js"   integrity="sha256-5i/mQ300M779N2OVDrl16lbohwXNUdzL/R2aVUXyXWA="   crossorigin="anonymous"></script>
    <script>
      const socket = io();
      $('form').submit(() => {
        socket.emit('chat message', $('#m').val());
        $('#m').val('');
        return false;
      });
    </script>

  </body>
</html>

```

チャット画面側から文字入力をするとNode.jsのコンソール側で表示されるのが確認できます。

![](https://i.gyazo.com/f4aa7fa3669aa33bafc73a12e51d9b1e.gif)

### 双方向にメッセージをブロードキャストする

* app.jsを編集

`io.emit('chat message', msg);`を加えただけです。

io.emit()で接続しているクライアントに対してメッセージを送信できます。

```app.js
const app = require('express')();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const PORT = process.env.PORT || 3000;

app.get(`/`, (req, res) => {
  res.sendFile(__dirname + '/index.html');
});

io.on('connection', (socket) => {
  console.log('a user connected');
  socket.on('chat message', (msg) => {
    console.log('message: ' + msg);
    io.emit('chat message', msg);
  });
});

http.listen(PORT, () => {
  console.log(`listening on *:${PORT}`);
});
```

* index.htmlを編集

こちらは受け取ったapp.js側のio.emit()で発火されたイベントを`socket.on()`で受け取ってDOMを追加しています。

```index.html
<!doctype html>
<html>
  <head>
    <title>Socket.IO chat</title>
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      body { font: 13px Helvetica, Arial; }
      form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
      form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
      form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
      #messages { list-style-type: none; margin: 0; padding: 0; }
      #messages li { padding: 5px 10px; }
      #messages li:nth-child(odd) { background: #eee; }
    </style>
  </head>
  <body>
    <ul id="messages"></ul>
    <form action="">
      <input id="m" autocomplete="off" /><button>Send</button>
    </form>

    <script src="/socket.io/socket.io.js"></script>
    <script src="https://code.jquery.com/jquery-3.1.1.slim.js"   integrity="sha256-5i/mQ300M779N2OVDrl16lbohwXNUdzL/R2aVUXyXWA="   crossorigin="anonymous"></script>
    <script>
        const socket = io();
        $('form').submit(() => {
            socket.emit('chat message', $('#m').val());
            $('#m').val('');
            return false;
        });
        socket.on('chat message', (msg) => {
            $('#messages').append($('<li>').text(msg));
        });
    </script>

  </body>
</html>
```

* 実行してみる

![](https://i.gyazo.com/a821db3098745408acfee24befdb8ced.gif)

これでチャットが完成しました！

ここまでのソースコードをGitHubにおいておきます。
yarn.lockというファイルがありますが[mizchiさんの記事](http://qiita.com/mizchi/items/1002fde0de10e7c54fb2)にもあるようにバージョン固定させるものみたいです。

> https://github.com/n0bisuke/express-socke.io-webapps

## Azure Web Appsにのせてみる

![](https://i.gyazo.com/f00ebccc955714a6fd21ebfe5221e2f8.png)

Azure Web AppsはHerokuやGAEのようにアプリケーションをホスティングできるPaaSです。
無料でけっこう使えるので、手前味噌ですが[とりあえずAWSを選ぶWeb制作者へ！「EC2でWebサーバーを立てる」をAzureでやってみた](https://liginc.co.jp/308378)なども読んで登録してみましょう。

今回はCLI経由でのインタンス立ち上げをやってみます。

### CLIからWeb Appsを立ち上げる

前回記事([AzureのCLIツールを使ってCoreOS/Docker上にWebサーバーを立ててみる。](http://qiita.com/n0bisuke/items/71444909769f2fc72455))を元にAzure CLIからログインしましょう。

ここでハマったのですが ([m2wasabi](http://qiita.com/m2wasabi)さんありがとうございました。)

* Resource Group
* App Service Plan

を先に自分で作成しておく必要があります。

すでに作ってる人は飛ばしましょう。

#### Resouce Groupの作成

東日本に`n0bisuke-test`という名前のResouce Groupを作成します。

```
$ azure group create n0bisuke-test "Japan East"
```

#### App Service Planの作成

`n0bisuke-test`に`KantoF1`というApp Service Planを作成します。FreeプランのF1を指定します。

```
$ azure appserviceplan create n0bisuke-test KantoF1 "Japan East" F1
```

参考: [Using Azure Resource Manager-Based XPlat CLI for Azure Web App](https://azure.microsoft.com/ja-jp/documentation/articles/app-service-web-app-azure-resource-manager-xplat-cli/)

>--tier: the desired pricing sku (The options are: F1 (Free). D1 (Shared). B1 (Basic Small), B2 (Basic Medium), and B3 (Basic Large). S1 (Standard Small), S2 (Standard Medium), and S3 (Standard Large). P1 (Premium Small), P2 (Premium Medium), and P3 (Premium Large).)

#### Web Appsのインスタンスを作成する

`n0bisuke-chat`という名前のインスタンスを作成します。

`azure webapp create`というコマンドで作成しますが、`$ webapp create [options] <resource-group> <name> <location> <plan>`という使い方になります。

```
$ azure webapp create n0bisuke-test n0bisuke-chat "Japan East" KantoF1
```

と、先ほどの`n0bisuke-test`と`KantoF1`を指定します。

```
info:    Executing command webapp create
                                                              + Creating webapp n0bisuke-chat
info:    Webapp n0bisuke-chat has been created
info:    webapp create command OK
```

無事にインスタンスが作成されました。

![](https://i.gyazo.com/f828547ec1d61c4f9439157ec382814f.png)

ブラウザから確認すると作成されていることが分かりますね。

`azure webapp show`というコマンドでも確認できます。

```
$ azure webapp show n0bisuke-test n0bisuke-chat
info:    Executing command webapp show
+ Getting webapp
data:
data:    Web App Name     : n0bisuke-chat
data:    Enabled          : true
data:    Status           : Running
data:    Last Modified    : 2016-10-07T03:49:06.730Z
data:    Location         : Japan East
data:    App Service Plan : KantoF1
data:
data:    Host Name
data:    -------------------------------
data:    n0bisuke-chat.azurewebsites.net
data:
info:    webapp show command OK
```

あとは、[Azure Web AppsにGitHub経由でNode.jsアプリケーションをデプロイする仕組みを無料で作る](https://liginc.co.jp/314601)を参照し、Node.jsアプリケーションをデプロイすればOKです。

![](https://i.gyazo.com/9d0140f3a1eca439a2266ede345a4fcd.gif)

動いた！！

### おまけ: ハマってたところ

`Resouce Group`までは分かってたのですが、`App Service Plan`が全然分からず、こんな指定をしてて怒られてました。

ブラウザ側で作成する際には`Resouce Group`は作るけど`App Service Plan`は作らなかったので見落としたのかと思います。

```
$ azure webapp create n0bisuke-test n0bisuke-chat japaneast F1
```

```
info:    Executing command webapp create
                                                              + Creating webapp chat
error:   {"Code":"NotFound","Message":"Cannot find ServerFarm with name F1.","Target":null,"Details":[{"Message":"Cannot find ServerFarm with name F1."},{"Code":"NotFound"},{"ErrorEntity":{"Code":"NotFound","Message":"Cannot find ServerFarm with name F1.","ExtendedCode":"51004","MessageTemplate":"Cannot find {0} with name {1}.","Parameters":["ServerFarm","F1"],"InnerErrors":null}}],"Innererror":null}
error:   Error information has been recorded to /Users/n0bisuke/.azure/azure.err
```

MSDNのフォーラムにも投稿してました笑

>[AzureのCLIからWebAppsアプリケーションを作成したい](https://social.msdn.microsoft.com/Forums/ja-JP/38776a36-280c-4cf8-af7f-cdf0b558a4db/azurecliwebapps?forum=windowsazureja)

ということでApp Service Planは先に作成しましょうね。


##まとめ

Express+Socket.ioで立ち上げたチャットアプリをWeb Appsにのせてみました。
前の投稿([AzureのCLIツールを使ってCoreOS/Docker上にWebサーバーを立ててみる。](http://qiita.com/n0bisuke/items/71444909769f2fc72455))もですがやはりCLI経由でいけると操作が早くて楽ですね。

あと、Socket.io久しぶりに触りましたけどやはり使い勝手がいいことを認識。