Node.js+Socket.IOで作るお手軽チャットアプリ

<a href="https://qiita.com/advent-calendar/2018/ateam-lifestyle-cyma">Ateam Lifestyle x cyma Advent Calendar 2018</a>の22日目は、株式会社エイチームで「cyma-サイマ-」のWebエンジニアをしている@ryo_cyが担当します。

今回は以前から気になっていたNode.jsを調べていたところ、リアルタイム通信を可能にするライブラリがあることを知りました。
なんだかすごく面白そうだなーと思ったので、簡易的なチャットアプリを作ってみました。
##事前説明
###Node.jsとは
Node.jsはスケーラブルなネットワークアプリケーションを構築するために設計された非同期型のイベント駆動の JavaScript 環境です。
いわゆるサーバーサイドのJavaScriptです。
サーバーサイドもクライアントサイドもJavaScriptで書くことができるので、両サイドを１つの言語で構築できるのは学習コストが低くなるためとてもメリットが大きいですね。
###Expressとは
Expressとは、Node.jsで利用できるWebアプリケーションフレームワークです。
ExpressはNode.jsで開発するとき非常にスピーディーにウェブアプリを構築できるフレームワークになっています。
ウェブアプリを開発しやすいように、開発の大枠を用意してくれるイメージです。
[詳しくは公式サイト](http://expressjs.com/)
###Socket.IOとは
Socket.IOは、ブラウザとサーバー間のリアルタイム、双方向、およびイベントベースの通信を可能にするライブラリです。
チャットアプリなどのリアルタイムに動くサービスを開発する際に必要となる処理を用意してくれています。
[詳しくは公式サイト](https://socket.io/)
##環境
下記環境で動作を確認しています。
macOS Sierra
バージョン 10.12.6
##事前準備
開発に必要なツールを準備します。
すでにインストールされている方は読み飛ばしてください。
今回はHomebrewをインストールして進めていきます。
###Homebrew
Mac OS Xオペレーティングシステム上でソフトウェアの導入を簡単にしてくれるパッケージ管理システムです。
[詳しいインストールについてはこちら](https://qiita.com/pypypyo14/items/4bf3b8bd511b6e93c9f9)
###nodebrewをインストール
Node.jsのバージョン管理ツールです。
複数バージョンのNode.jsをインストールしたり、切り替えなどができます。

```
$ brew install nodebrew
```

####環境パスを通す
続いて環境パスを通します。
以下はbashの場合になっておりますので適宜転換してください。

```
$ echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.bash_profile
$ source ~/.bash_profile 
```

###Node.jsをインストール
Node.jsをインストールしていきます。
以下のコマンドでインストールできるバージョンが確認できます。

```
$ nodebrew ls-remote
```

必要なバージョンをインストールしていきましょう。
通常使用する場合、先頭の数字が偶数のバージョンが推奨版になっています。
奇数のバージョンは新しい機能が追加され、確認しているバージョンになります。
今回はv10.14.2(記事執筆時の最新)をインストールしていきます。

```
$ nodebrew install-binary v10.14.2
```

以下のようなエラーが出る場合はディレクトリを制作しましょう。

```
Fetching: https://nodejs.org/dist/v10.14.2/node-v10.14.2-darwin-x64.tar.gz
Warning: Failed to create the file 
Warning: /Users/username/.nodebrew/src/v10.14.2/node-v10.14.2-darwin-x64.tar.gz
Warning: : No such file or directory
                                                                           0.0%
curl: (23) Failed writing body (0 != 847)
download failed: https://nodejs.org/dist/v10.14.2/node-v10.14.2-darwin-x64.tar.gz
```

以下のコマンドでディレクトリを制作

```
$ mkdir -p ~/.nodebrew/src
```

インストールされているか以下コマンドで確認しましょう。

```
$ nodebrew ls
```

無事インストールされた場合は、以下のコマンドを実行しNode.jsを有効化します。

```
$ nodebrew use v10.14.2
```

Nodeのバージョンを確認してインストールができていれば完了です。

```
$ node -v
```

バージョン確認時に以下のエラーが出る場合は、パスが読み込まれていない状態です。

```
-bash: node: command not found
```

.bash_profileに下記を記述します。

```
if [ -f ~/.bashrc ] ; then
. ~/.bashrc
fi
```

###npm initでプロジェクトを初期設定
チャットアプリ用に空ディレクトリを制作し、npm initコマンドでプロジェクトを初期化、設定し、パッケージをインストールするための準備を行います。

```
$ mkdir chat_app
$ cd chat_app
$ npm init

#プロンプトに回答します。
```

プロンプトに回答するとpackage.jsonが出来上がります。
このpackage.jsonはプロジェントの名前や概要、パッケージの依存関係を定義しています。
###Expressをインストール
続いてExpressをコマンドでインストールしていきます。

```
$ npm install express --save
```

Expressインストール実行後、自動で生成されるpackage.jsonを確認するとdependenciesの中にExpressが追加されていることがわかります。

```json:package.json
{
  "name": "chat_app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "ryo_cy",
  "license": "MIT",
  "dependencies": {
    "express": "^4.16.4"
  }
}
```

###Socket.IOをインストール
続いてSocket.IOをExpressと同様にインストールしていきます。

```
$ npm install socket.io
```

無事インストールが完了したら、準備が完了です。
##構築
ここからは実際に構築を行っていきます。
###Hello World
まずはブラウザ上に「Hello World!」を表示してみたいと思います。
下記のようにapp.jsを制作します。

```js:app.js
const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => res.send('Hello World!'))

app.listen(port, () => console.log(`Example app listening on port ${port}!`))
```

そして、以下のコマンドを実行することで、サーバーが立ち上がったことと、使用するポート番号がわかります。

```bash
$ node app
Example app listening on port 3000!
```

今回は3000番のポートを参照していますので、http://localhost:3000 にアクセスします。
ブラウザでHello World!を確認できます。
![hello_world.png](https://qiita-image-store.s3.amazonaws.com/0/316574/481bab7e-9172-08fc-173f-6d91d4abf7e7.png)
###HTMLファイルを表示
現在は文字列を表示させているだけなので、htmlファイルを表示するように変更します。
先程制作したapp.jsのapp.getの内容を書き換えます。

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

そしてhtmlファイルを制作します。

```html:index.html
<!DOCTYPE html>
<html>
<head>
  <title>チャットアプリをつくってみよう</title>
  <style>
    * {margin:0; padding:0; box-sizing:border-box; }
    body { font-size: 14px; max-width: 600px; width: 100%; margin: 0 auto; padding: 0 15px; }
    .chatSend { padding: 3px; position: fixed; bottom: 0; width: 100%; }
    .chatText { border: 2px solid #ccc; padding: 10px; width: 90%; margin-right: .5%; border-radius: 5px; }
    .sendButton { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; border-radius: 5px; }
    #timeline { list-style-type: none; margin: 0; padding: 0; }
    #timeline li { padding: 5px 10px; }
    #timeline li:nth-child(odd) { background: #eee; }
  </style>
</head>
<body>
  <h1>チャットアプリをつくってみよう</h1>
  <ul id="timeline"></ul>
  <form id="chatSend" action="">
    <input id="chatText" autocomplete="off"><button class="sendButton">送信</button>
  </form>
</body>
</html>
```

そしてサーバーを再起動します。
`control+c`でサーバーを終了し、`$ node app`で再度起動しましょう。
起動後に以下のようなが画面が表示されれば正常にhtmlファイルが読み込まれています。
<img width="625" alt="chat01.png" src="https://qiita-image-store.s3.amazonaws.com/0/316574/4b267673-1281-b27e-d460-fad9e65a1cc2.png">
###Socket.IOを使ってチャット機能を実装
ここからは、リアルタイム通信を行うライブラリであるSocket.IOを組み込んでいきましょう。
まずは`app.js`を編集します。

```js:app.js
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);

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

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

http.listen(3000, function(){
  console.log('listening on *:3000');
});
```

続いて、`index.html`側にも処理を追加するため、以下のコードを`</body>`の前に追加します。

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

一度サーバーを再起動し、ローカル環境へアクセスすると、ターミナルへ以下のように`a user connected`とメッセージが表示されるようになります。

また、新規タブを開きローカル環境へのアクセスを実行すると、アクセスの数だけコンソールにも`a user connected`と表示されるのでリアルタイムで処理が動いているのが確認できます。

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

このままではチャットとして使用できないため、サーバー側、クライアント側の両方に処理を追加していきます。

```js:app.js
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);

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

//io.onに処理を追加
io.on('connection', function(socket){
  socket.on('chat message', function(msg){
    io.emit('chat message', msg);
  });
});

http.listen(3000, function(){
  console.log('listening on *:3000');
});
```

`io.on`処理に、`io.emit('chat message', msg);`を追加しました。
サーバー側で受け取ったメッセージを閲覧ユーザー全員へ発信することができます。

最後に、サーバー側から受け取ったメッセージをブラウザ上へ表示する処理を`index.html`へ追記します。

```html
<!DOCTYPE html>
<html>
<head>
	<title>チャットアプリをつくってみよう</title>
	<style>
		* {margin:0; padding:0; box-sizing:border-box; }
		body { font-size: 14px; max-width: 600px; width: 100%; margin: 0 auto; padding: 0 15px; position: relative; }
		#chatSend { padding: 3px 15px; position: fixed; bottom: 5px; width: 100%; max-width: 600px; }
		#chatText { border: 2px solid #ccc; padding: 10px; width: 90%; margin-right: .5%; border-radius: 5px; }
		#sendButton { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; border-radius: 5px; }
		#timeline { list-style-type: none; margin: 0; padding: 0; }
		#timeline li { padding: 5px 10px; }
		#timeline li:nth-child(odd) { background: #eee; }
	</style>
</head>
<body>
	<h1>チャットアプリをつくってみよう</h1>
	<ul id="timeline"></ul>
	<form id="chatSend" action="">
		<input id="chatText" autocomplete="off"><button id="sendButton">送信</button>
	</form>
	<!-- scriptの処理を追加 -->
	<script src="/socket.io/socket.io.js"></script>
	<script src="https://code.jquery.com/jquery-1.11.1.js"></script>
	<script>
		$(function () {
			var socket = io();
			$('form').submit(function(){
				socket.emit('chat message', $('#chatText').val());
				$('#chatText').val('');
				return false;
			});
			socket.on('chat message', function(msg){
				$('#timeline').append($('<li>').text(msg));
			});
		});
	</script>
</body>
</html>
```
`io.emit`から発信された情報を`socket.on`で受け取ることによって、ブラウザ上へ表示する処理を行っています。

それではサーバーを再度再起動し、実際にチャットを動かしてみたいと思います。
![chat_movie2.gif](https://qiita-image-store.s3.amazonaws.com/0/316574/cfb5e289-afbc-23f4-e0c9-648e8792cde8.gif)

無事に動いているのが確認できました。
##まとめ
今回初めてNode.jsに触れてみましたが、簡易的とはいえチャットアプリをこんなに簡単に作れてしまうことに驚きました。

Node.jsには、今回使用したライブラリ以外にも様々なツールがあるので今後も期待が高まります。

<a href="">Ateam Lifestyle x cyma Advent Calendar 2018</a> の23日目は同じく「cyma-サイマ-」の@minami-nuさんです。お楽しみに！

***

エイチームグループでは、一緒に働けるチャレンジ精神旺盛な仲間を募集しています。興味を持たれた方はぜひエイチームグループ採用サイトを御覧ください。
[https://www.a-tm.co.jp/recruit/](https://www.a-tm.co.jp/recruit/)