【Express】express-generatorで作成した雛形でSocket.IOを使う【Node.js】

## はじめに
タイトルの通り[Socke.IO](https://socket.io/)を[express-generator](https://www.npmjs.com/package/express-generator)で作成した雛形に載せたい場合のチュートリアル的なものになります。
[公式のchat application](https://socket.io/get-started/chat)をやってみて、「じゃあこれをexpress-generatorの雛形で使うにはどうすればいいのだ？」って人向けの記事になります。

## Socket.IOってなんじゃ？
WebSocketを実装するためのモジュールです。
WebSocketっていうのはブラウザとウェブサーバーとの間で双方向通信を行うための通信規格のことで（詳しくは調べてください…）
これを使えばチャットみたいなリアルタイムWebアプリケーションを作成できます！

## やること
[chat application](https://socket.io/get-started/chat)をexpress-generatorの雛形で作っていきます。
※今回の手順は元のやつと前後します

## 環境
- Node,js：18.12.0
- npm：9.2.0
- Express：4.18.2
- EJS：3.1.8（別に必須ではない）
- Socket.IO：4.6.0


## 1.雛形を作成
いつものやつ（テンプレートエンジンはejs）。
```
$ npx express-generator --view=ejs <アプリ名>
$ cd <アプリ名>
$ npm install
```

## 2.Socket.IOのモジュールをインストール
```
$ npm install socket.io
```

## 3.クライアント側のSocketを作成
`public/javascripts/clientSocket.js`として以下を作成する。
※読み込み方さえ間違えなければファイル名や配置場所はどこでもよいです
```javascript:clientSocket.js
const socket = io();

const messages = document.getElementById('messages');
const form = document.getElementById('form');
const input = document.getElementById('input');

form.addEventListener('submit', (e) => {
  e.preventDefault();
  if (input.value) {
    socket.emit('chat message', input.value);
    input.value = '';
  }
});

socket.on('chat message', (msg) => {
  const item = document.createElement('li');
  item.textContent = msg;
  messages.appendChild(item);
  window.scrollTo(0, document.body.scrollHeight);
});
```

## 4.クライアント側の画面を作成
新しく画面を作成するとapp.jsいじったりが面倒なので、今回は`views/index.ejs`を以下で上書きします。
```html:index.ejs
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Socket.IO chat</title>
  <style>
    body { margin: 0; padding-bottom: 3rem; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }

    #form { background: rgba(0, 0, 0, 0.15); padding: 0.25rem; position: fixed; bottom: 0; left: 0; right: 0; display: flex; height: 3rem; box-sizing: border-box; backdrop-filter: blur(10px); }
    #input { border: none; padding: 0 1rem; flex-grow: 1; border-radius: 2rem; margin: 0.25rem; }
    #input:focus { outline: none; }
    #form > button { background: #333; border: none; padding: 0 1rem; margin: 0.25rem; border-radius: 3px; outline: none; color: #fff; }

    #messages { list-style-type: none; margin: 0; padding: 0; }
    #messages > li { padding: 0.5rem 1rem; }
    #messages > li:nth-child(odd) { background: #efefef; }
  </style>
</head>
<body>
  <ul id="messages"></ul>
  <form id="form" action="">
    <input id="input" autocomplete="off" /><button>Send</button>
  </form>
  <!-- クライアントのSocket.ioモジュールと作成したjsを読み込む-->
  <script src="/socket.io/socket.io.js"></script>
  <script src="/javascripts/clientSocket.js"></script>
</body>
</html>
```
（CSSは直書きなのかよ）

## 5.サーバー側のSocketを作成
サーバー側のSocket.IOの処理を関数として作成する。
（表現としても置く場所としても正しいのか不明だが一旦こういう手法で進める）
アプリのルートディレクトリに新たに`function`ディレクトリを作成し、その中に`function/chat.js`を作成する。
```javascript:chat.js
const { Server } = require('socket.io');

const chat = (server) => {
  const io = new Server(server);
  io.on('connection', (socket) => {
    console.log('a user connected');
    socket.on('chat message', (msg) => {
      io.emit('chat message', msg);
    });
    socket.on('disconnect', () => {
      console.log('user disconnected');
    });
  });
}

module.exports = chat;
```
元々やっていることとしてはcreateServerメソッドで作成したHTTPサーバーオブジェクトを引数にsocket.ioのインスタンスを作成しているだけなので、外部関数を作成する際もそこを考えて記述すればよい。
module.exportsも忘れずに。

余談ですが、`const { Server } = require('socket.io');`の中括弧はなんやねんと思った方、オブジェクトの分割代入という構文らしいです。
やってることは以下のような感じ。
```javascript:sample.js
const obj = {a: 10, b: 20, c: 30};
const {a, c} = obj;

console.log(a); // 10
console.log(c); // 30
```

## 6.bin/wwwを修正
「HTTPサーバーオブジェクトを引数にsocket.ioのインスタンスを作成する」ということでHTTPサーバーオブジェクトを作ったところで呼び出します。
以下の記述を`bin/www`に加えます。
```javascript:www
#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
var debug = require('debug')('sample-socket.io-express-generator:server');
var http = require('http');
const chat = require('../function/chat'); // 作成したサーバー側Socketの外部関数を読み込み

// （中略）

/**
 * Create HTTP server.
 */

var server = http.createServer(app);

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

server.listen(port, () => {
  console.log(`start listening on *:${port}`);
});
server.on('error', onError);
server.on('listening', onListening);

/**
 * ADDED!! Socket.IO Connection.
 */

chat(server); // serverを引数にサーバー側Socketを起動

// （省略） 
```

## 7.サーバーを起動

```
$ npm start
```
[https://localhost:3000/](https://localhost:3000/)に接続してリアルタイムチャットができることを感じる。
めでたし。

## おわりに
Socket.IO×Express（Node.js）の情報が散らばってるなと感じていたので、まとめてみました。
自分と同じ悩みを持った人の役に立てばよいなと思います。
`app.use`とか使ったらもっとキレイに書けるのかなとも思ったり…。

## 参考
- [chat application - Socket.IO](https://socket.io/get-started/chat)
- [分割代入 - JavaScript（MDN Web Docs）](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)