[チャット]SNSのテンプレートを作成してみた[解説編]

# はじめに
　SNSテンプレートの完成版はすでにGitHubのリポジトリに上げています。
　この記事ではそのリポジトリを元に、node.jsとexpressとsocket.ioの紹介をします。なお、環境作成としてリポジトリに上げているmongodbとnginxの解説はほとんど行いませんので悪しからず。

リポジトリのクローンなどを雑に解説したヤツ

https://qiita.com/king_dog_fun/items/fb833a71115199c02c39

# serverのディレクトリ構成

　express-generatorで生成される雛型を元に構成しています。大きな変更点と言えばconfigディレクトリの追加とsocket.jsの作成でしょうか。
　express-generatorのインストールと実行は次のコマンドで行うことが出来ます。試してみてね☆

```
npm install express-generator -g
express --view=pug directory_name
```

　ちーなーテンプレートエンジンはpugです。
　以下に書いてある以外にもファイルは存在しているのですが、チャットという処理に特別大事そうなファイルだけをピックアップしています。

```
.
├── bin
│   └── www
├── config
│   └── socket.js
├── routes
│   └── test.js
├── views
│   └── test.pug
└── mongo.js
```

# モジュールのインストール

## socket.io

```
npm install socket.io
```

　割と何の問題もなく普通にインストールするだけです。

# www

```javascript:bin/www
/**
 * Create HTTP server.
 */

var server = http.createServer(app);

// socket.io
require("../config/socket")(server); //<-- 追加

/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(port);
```

　このexpress-generatorのエントリーポイント、binディレクトリに入っているwwwファイルを編集しています。ただしあまり大きくいじって見辛くしたくないので一行だけ追加しています。後の処理はすべてconfigにあるsocketに詰め込みましょう。

# socket.js

```javascript:config/socket.js
const socketIo = require('socket.io');
const mongo = require('../mongo.js');
const col = mongo.col('chat');

module.exports = function (server) {
  const io = socketIo(server);
  io.on('connection', (socket) => {
    console.log('user connected');
    socket.on('sendMessage', (message) => {
      console.log('Message has been sent: ', message);
      col.insertOne({msg:message});
      // 'receiveMessage' というイベントを発火、受信したメッセージを全てのクライアントに対して送信する
      io.emit('receiveMessage', message);
    });
  });

};
```

　ここでは引数にserverを引っ張ってきていますが、参考にするところによるとapp変数でも可能のようです。ただ僕自身は多く試すのもめんどくさかったためapp変数でsocketサーバーを立ち上げてみたことがありません。
　app変数でsocketサーバーの立ち上げが出来るというなら、app.jsにwwwに追加したプログラムを移していいかもしれませんが、そこはご自身で確認してみてください。そしてあわよくば僕にもお教えください。

## io.on "connection"

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

　socket通信の際にclient側から、ここでならtest.pugからsocket通信が試みられ、最初のコネクションが成立する際にio.on("connection,の関数が起動します。

## socket.on "sendMessage"

```javascript
    socket.on('sendMessage', (message) => {
      console.log('Message has been sent: ', message);
      col.insertOne({msg:message});
      // 'receiveMessage' というイベントを発火、受信したメッセージを全てのクライアントに対して送信する
      io.emit('receiveMessage', message);
    });
```

　sendMessageという要素でsocket通信がされてくれば、そのデータとともにsocket.on('sendMessage',のイベント関数が発火します。messageが送られてきた文字データですね。
　そしてcol.insertOneとすることでMongoDBのchatコレクションに送られてきた情報を保存して、尚且つサーバーから各クライアントに向けて送信しなおしているのが、io.emit('receiveMessage', message);です。

# clietn側

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2647158/322470a3-d467-1a45-baad-303b920a18e0.png)

　こんな感じの画面になる。チャットをしてみれば次みたいな感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2647158/28c0eaec-f7ce-0b32-e1c0-cb711bd301dc.png)


## test.js

```javascript:routes/test.js
var express = require('express');
var router = express.Router();
const mongo = require('../mongo.js');
const col = mongo.col('chat');

/* GET home page. */
router.get('/', async function(req, res, next) {
  let result = await col.find().toArray();
  res.render('test',{result:result});
});

module.exports = router;
```

　test.pugにアクセスしてもらった際に、ページを返すための関数。
　先ほどcol.insertOneで保存するといったデータをページに表示させるためにresultを取得して、pugエンジンに投げています。

## test.pug

```pug:test.pug
html
  head
    script(src="/socket.io/socket.io.js")
  body
    h1 simple chat
    input#inputText(type="text")
    input#sendButton(type="submit")
    ul#messageList
      each val in result
        li= val.msg
  script.
    const socket = io();
    
    const clearText = () => {
    document.getElementById('inputText').value = '';
    }
    
    const addMessageList = (message) => {
    const ul = document.getElementById('messageList');
    const li = document.createElement('li');
    const text = document.createTextNode(message);
    li.appendChild(text);
    ul.appendChild(li);
    };
    
    document.getElementById('sendButton').addEventListener('click', () => {
    let inputMessage = document.getElementById('inputText').value;
    
    if (inputMessage === '') {
    return;
    }
    
    socket.emit('sendMessage', inputMessage);
    clearText();
    });
    
    //  'receiveMessage' イベントの発火を検知
    //  第一引数には受信したメッセージが入る
    socket.on('receiveMessage', (message) => {
    // 受信したメッセージをulタグに挿入
    addMessageList(message);
    });
```

　テンプレートエンジンがpugのため多くの人にはわかり辛い内容になってますかね、次にejsに変換した方を載せておきます。

## test.ejs

```html:test.html

<html>
  <head>
    <script src="/socket.io/socket.io.js"></script>
  </head>
  <body>
    <h1>simple chat</h1>
    <input id="inputText" type="text"/>
    <input id="sendButton" type="submit"/>
    <ul id="messageList">
      <% for (var val of result) { %>
        <li>
          <%= val %>
        </li>
      <% } %>
    </ul>
  </body>
  <script>
    const socket = io();
    
    const clearText = () => {
      document.getElementById('inputText').value = '';
    }
    
    const addMessageList = (message) => {
      const ul = document.getElementById('messageList');
      const li = document.createElement('li');
      const text = document.createTextNode(message);
      li.appendChild(text);
      ul.appendChild(li);
    };
    
    document.getElementById('sendButton').addEventListener('click', () => {
      let inputMessage = document.getElementById('inputText').value;
    
      if (inputMessage === '') {
        return;
      }
    
      socket.emit('sendMessage', inputMessage);
      clearText();
    });
    
    //  'receiveMessage' イベントの発火を検知
    //  第一引数には受信したメッセージが入る
    socket.on('receiveMessage', (message) => {
      // 受信したメッセージをulタグに挿入
      addMessageList(message);
    });
  </script>
</html>
```

　単純なhtmlで書けないのは、resultからデータを出力するため。

### scriptインクルード

```html
<script src="/socket.io/socket.io.js"></script>
```

　鋭い方はこのタグを見て、あれ？こんなファイル用意してなかったよね、と疑問に思うかもしれません。
　しかし、ここではsocket.ioをインストールして、configディレクトリのsocket.jsを適切に起動してさえいれば、ファイルを置かなくてもこのタグはエラーを吐きません。
　そういうものだと思ってください。

### io()

```javascript
    const socket = io();
```

　io();でserverとのコネクションを確立させています。
　ioの引数にはURLを指定することも可能ですが、何も指定しなかった場合、window.location.hrefを取得してくれるみたいです。

### socket.emit

```javascript
    document.getElementById('sendButton').addEventListener('click', () => {
      let inputMessage = document.getElementById('inputText').value;
    
      if (inputMessage === '') {
        return;
      }
    
      socket.emit('sendMessage', inputMessage);
      clearText();
    });
```

　入力欄に入力された文字が存在する場合、ボタンを押すことでサーバー側へデータを送信することが出来るメソッドです。
　サーバー側のconfig/socket.jsでいうと

```javascript
    socket.on('sendMessage', (message) => {
```

　のイベント関数が発火することとなります。


### socket.on

```javascript
    socket.on('receiveMessage', (message) => {
      // 受信したメッセージをulタグに挿入
      addMessageList(message);
    });
```

　サーバー側からsocket通信が飛んできた際に発火するイベント関数です。

```javascript
    socket.on('sendMessage', (message) => {
      console.log('Message has been sent: ', message);
      col.insertOne({msg:message});
      // 'receiveMessage' というイベントを発火、受信したメッセージを全てのクライアントに対して送信する
      io.emit('receiveMessage', message);
    });
```

　さきほどのsendMessageがどこかのクライアントから発信されると、receiveMessageが全体に発信されることで、全体のチャットが更新されます。

# 終わりに
　チャットのプログラムとして大切そうな部分をざつ～に解説しました。このような解説はもっと丁寧なものが他にもいくつも存在しているのですが、express-generatorを基礎としてやっているものは数少ないように感じます。そのため、比較するものも少ないこともあり、僕が公開しているSNSのテンプレートもこれが最適解と豪語出来ません。
　改良点や必要な修正、こうしたほうがいいよなどなどありましたらGitHubの方でもTwitterの方でもいいので教えていただきたいです。

Twitter:

https://twitter.com/dog_king_fun

GitHub:

https://github.com/DogKingFun/sns_template