Node.js + Express + Socket.ioで簡易チャットを作ってみる

#Node.js + Express + Socket.ioで簡易チャットを作ってみる

詳しくはSocket.ioのドキュメントをご覧ください。
[https://Socket.io/docs/](https://socket.io/docs/)

##やりたいこと
Node.js+Socket.ioの組み合わせはよく知られていると思います。
今回はこの組み合わせを使用してチャットを作ってみることを目標にしたいと思います。

##Socket.ioとは
Socket.ioについて紹介する前に、WebSocketとは何かの説明をする必要がありますので、WebSocketとは何者かを解決していきたいと思います。

###WebSocketとは
WebSocket（RFC6455）はリアルタイムかつ双方向な通信を実現するプロトコルです。
元々はHTML5の仕様の一部として進められていましたが、現在では単独のプロトコルとして策定されています。
WebSocket通信では、コネクション確立時にHTTPからWebSocketにプロトコルを切り替え、```ws:```または```wss:```から始まるURIスキーム上で
```クライアント⇄サーバ間```のデータのやりとりを行います。

###AjaxやCometとの相違点
WebSocketが他のHTTPベースのプロトコルと異なるのは```ステートフルな通信である```という点です。
HTTP(Ajax,Comet)では通信のたびに新たにコネクションを確立する必要がありますが、WebSocketではその必要がありません。
また、HTTPより軽量なヘッダを扱うことから、通信コストが低い利点もあり、
よりリアルタイム性の高いプロトコルであることがWebSocketの特徴です。

###Socket.ioは何？
複雑なリアルタイムWeb技術の実装方式を隠蔽し、すべてのブラウザ・モバイルデバイスでリアルタイム通信を可能とすることを目的に開発されているnode.js用サーバー側ライブラリとブラウザ用JavaScriptライブラリになります。

各技術(Ajax、Comet、WebSocket)に対応しているため、Socket.ioのAPIを利用してサーバー側・ブラウザ側の実装を行うことで、OSやブラウザによって、最適なリアルタイムWeb技術が選択され、どのような環境からでもリアルタイム通信を行うことが可能になります。


##いざ実装
###準備
まず、アプリを作成するためのディレクトリを作成します。

```shell
$ mkdir <app name> && cd <app name>
```
ディレクトリを作成し、ディレクトリ内に移動した状態で

```shell
$ npm init
```

を実行し、package.jsonを作成します。
今回作成したpackage.jsonはこんな感じになりました。

```json
{
  "name": "nodesample",
  "version": "1.0.0",
  "description": "node.js+Express+Socket.io",
  "main": "app.js",
  "author": "",
  "license": "ISC"
}
```
package.json の中で最も大事な項目は "name" と "version" です。必須であり、パッケージはこれらなしで インストール出来ません。name と version をもってして、パッケージが 完全に一意となることが想定されています。よってパッケージ内容を変更するには version を変更しなければなりません。

また、今回はNode.js+Socket.ioの他にNode.jsの有名なフレームワークであるExpressを利用します。

それでは、ExpressとSocket.ioをインストールします。

```shell
$ npm install express
$ npm install socket.io
```

インストールが完了したら、アプリを作り始めるための準備が完了です。

###Hello world!!
まず、定番のHello Worldをしてみましょう。

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

app.get('/' , function(req, res){
   res.send('hello world');
});

http.listen(PORT, function(){
  console.log('server listening. Port:' + PORT);
});
```
上記のようにapp.jsを作成しましょう。
そして、以下のコマンドを実行するとサーバーが立ち上がったことと、ポートがわかると思います。

```shell
$ node app
server listening. Port:7000
```

ちなみに、
```PORT = process.env.PORT || 7000;```
はPaaSなどにWebアプリとし公開している場合にはprocess.env.PORTの値を参照し、ローカルで確認する場合には7000番のポートを参照するという処理になります。

今回は、ローカルで実行しているので、指定した7000になりますね。

サーバーを立ち上げた状態で、
[http://localhost:7000](http://localhost:7000)にアクセスすると、hello worldを確認できます。
![スクリーンショット 2018-06-08 16.40.12.png](https://qiita-image-store.s3.amazonaws.com/0/255007/b7dcad6e-23c7-e2ad-4fce-670bd1a4f808.png)


###HTMLファイルを送ろう
現在は文字列を直接送ってしまっているので、次はHTMLファイルを送信しましょう。
app.jsを以下のように書き換えます

```javascript:app.js
var express = require('express');
var app = express();
var http = require('http').Server(app);
const PORT = process.env.PORT || 7000;

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

http.listen(PORT, function(){
  console.log('server listening. Port:' + PORT);
});
```
また、該当するindex.htmlをapp.jsと同じディレクトリに作成します、

```html:index.html
<!DOCTYPE html>
<html>
<head>
	<title>socket.io chat</title>
</head>
<body>
	<h1>Socket IOを使って見よう</h1>
</body>
</html>
```

ここまでできたらもう一度サーバーを立ち上げて、確認してみましょう。

以下のような画面が表示されれば成功です。
![スクリーンショット 2018-06-08 17.02.43.png](https://qiita-image-store.s3.amazonaws.com/0/255007/fea15edc-8f53-e21f-a97b-2a9bcee8f7ad.png)


##いよいよsocket.ioを使ってみる
それではいよいよSocket.ioを使用してみましょう。
moduleはすでに利用できる環境ですので、こちらをrequireすることで利用していきます。

それでは、socket.ioで必要になる処理をapp.jsに追記していきます。

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

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

io.on('connection',function(socket){
	console.log('connected');
});

http.listen(PORT, function(){
	console.log('server listening. Port:' + PORT);
});
```

また、サーバーサイド側だけでなく、クライアント側にもsocket.ioの処理記載する必要があるので、index.htmlの```<head></head>```内に処理を記載します。

```html:index.html
<!DOCTYPE html>
<html>
<head>
	<title>socket.io chat</title>
	<script src="/socket.io/socket.io.js"></script>
</head>
<body>
    <ul id="messages"></ul>
    <form>
      <input id="input_msg" autocomplete="off" /><button>Send</button>
    </form>
  <script>
　　　var socket = io();
　</script>
</body>
</html>
```

これで、もう一度サーバーを立ち上げてアクセスすると、コンソール上にconnectedと表示されていると思います。
<img width="306" alt="スクリーンショット 2018-06-08 18.48.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/255007/d6d8bb7f-7413-48b2-61c6-7ecf5bfbe2e0.png">


ここまでできたならあとはメッセージの送受信を行うだけですので、そのメッセージのやりとりを行う処理を記載していきましょう。

説明の前に処理を記載します。

まずはクライアント→サーバーの一方方向に対してメッセージを送る処理を書きます。

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

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

io.on('connection',function(socket){
	socket.on('message',function(msg){
		console.log('message: ' + msg);
	});
});

http.listen(PORT, function(){
	console.log('server listening. Port:' + PORT);
});
```
```html:index.html
<!DOCTYPE html>
<html>
<head>
	<title>socket.io chat</title>
	<script src="/socket.io/socket.io.js"></script>
	<script src="https://code.jquery.com/jquery-1.11.1.js"></script>
</head>
<body>
    <ul id="messages"></ul>
    <form id="message_form" action="#">
      <input id="input_msg" autocomplete="off" /><button>Send</button>
    </form>
  <script>
	  var socketio = io();
	  $(function(){
		  $('#message_form').submit(function(){
	        socketio.emit('message', $('#input_msg').val());
	        $('#input_msg').val('');
	        return false;
	      });
	    });
	</script>
</body>
</html>
```
記述したコードについて説明していきたいと思います。socket.ioを利用するにあたり重要となるのは、onとemitになります。
socketio.emit(<イベント名>, data) でイベントを発火(=データの送信)をし、
socketio.on(<イベント名>, callback) でイベントを検知(=データの受信)することができます。
上述のコードで言えば今回はクライアント側であるindex.html上で、データの送信（emit）をし、サーバー側のapp.jsで発火されたイベントの検知(on)をおこなうことでデータのやりとりを行なっています。

ここまで記載したらもう一度サーバーを立ち上げてhttp://localhost:7000 にアクセスして、メッセージを複数送信してみてください。
コンソール上に入力したメッセージが表示されていると思います。
<img width="381" alt="スクリーンショット 2018-06-08 19.23.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/255007/8f4b786d-da86-0775-3e87-3aa2dc84bc5c.png">


確認できたらクライアントからサーバーに対する一方方向の送信の処理が終了しました。

次は、クライアントから受け取った情報をサーバがクライアントに対して送信する処理(送受信)を記載していきます。

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

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

io.on('connection',function(socket){
	socket.on('message',function(msg){
		console.log('message: ' + msg);
		io.emit('message', msg);
	});
});

http.listen(PORT, function(){
	console.log('server listening. Port:' + PORT);
});
```
サーバー側では、受け取ったメッセージを接続しているクライアント全員に対して送信する以下の一行を追加しています。
```io.emit('message', msg);```
これによってサーバーがクライアントから受け取ったメッセージを全員に対して発信することができます。

また、クライアント側(index.html)でもサーバーから受け取った情報を表示する処理を記載したいと思います。

```html:index.html
<!DOCTYPE html>
<html>
<head>
	<title>socket.io chat</title>
	<script src="/socket.io/socket.io.js"></script>
	<script src="https://code.jquery.com/jquery-1.11.1.js"></script>
</head>
<body>
    <ul id="messages"></ul>
    <form id="message_form" action="#">
      <input id="input_msg" autocomplete="off" /><button>Send</button>
    </form>
  <script>
	  var socketio = io();
	  $(function(){
		  $('#message_form').submit(function(){
	        socketio.emit('message', $('#input_msg').val());
	        $('#input_msg').val('');
	        return false;
	      });
	      socketio.on('message',function(msg){
	      	$('#messages').append($('<li>').text(msg));
	      });
	    });
	</script>
</body>
</html>
```

```
socketio.on('message',function(msg){});
```
サーバーから発火された’message’イベントを検知し、受け取ったメッセージを表示する処理を行なっています。
上述のコードでは、html上の```<ul id="messages">```に対してメッセージを追加しています。

ここまでできたら、サーバーをもう一度立ち上げ、http://localhost:7000 にアクセスし、実際に入力し投稿してみましょう。

以下のようにリアルタイムに通信ができていたら成功です。
![pue.gif](https://qiita-image-store.s3.amazonaws.com/0/255007/68618042-72fe-b788-5691-4cdca1f5c3d0.gif)


お疲れ様でした。