HerokuでExpressとWebSocketを使った簡易チャットを作る

# 今回の内容
前々回、[HerokuでExpressアプリを作る---基本設定](https://qiita.com/forl_head_officer/items/4af1661bb3b586624a6e)、前回、[ExpressとPostgresqlを使ってHerokuでログインページを作る](https://qiita.com/forl_head_officer/items/01491a5ceb85e084c0df)、の続き。
今回はExpressをWebSocketサーバーとしてチャットを作る。ExpressでのWebSocket関連は色々な解説があるが`express-ws`を使うのが便利だった[^ref]。
クライアント側には素の[WebSocket](https://developer.mozilla.org/ja/docs/Web/API/WebSockets_API)のみを使った。

## サーバー側の実装
`$ yarn add express-ws`でexoress-wsをインストールする。express-wsはappをセットするだけで`app.ws(path, callback)`の形で使えるようになる
前々回までの基本部分、以外の追加分は

```js:index.js
const expressWs=require('express-ws')(app);
const chat=require('./src/chat');

//*** 略 ***//

app.ws('/chat', chat);
```
実装は`src/chat.js`と別ファイルにしておくデータのやり取りはJSON形式を使う。clientにはnameプロパティがなかったのでここに接続ユーザー名を付けておく

```js:src/chat.js
let clients=[];

const chat=(ws, req)=>{
    clients.push(ws);

    ws.on('message', message=>{
        const json=JSON.parse(message);
        clients.forEach(a=>{ if( a===ws && a.name==null ) a.name=json.user });

        if( json.method!=null ) json.clients=clients.map(a=> a.name);

        clients.forEach(a=>{ a.send(JSON.stringify(json)); });
    });
    ws.on('close', ()=>{
        const name=clients.find(a=> a===ws).name;
        clients=clients.filter(a=> a!==ws);
        clients.forEach(a=>{ a.send(JSON.stringify({ user: name, method: 'close', clients: clients.map(a=>name) })); });
    });
}

module.exports=chat;
```

## クライアント側実装
```js:js/chat.js
window.addEventListener('DOMContentLoaded', ()=>{
    const name=prompt('名前を入れてください');
    document.getElementById('name').value=name;

    const ws=new WebSocket(location.origin.replace('https', 'wss')+'/chat');
    ws.addEventListener('open', ()=>{ ws.send(JSON.stringify({ user: name, method: 'connect' })); });

    const input=document.getElementById('input');
    document.getElementById('send').addEventListener('click', ()=>{
        const text=input.value;
        if( text.length>0 ){
            const json={ user: name, message: text };
            ws.send(JSON.stringify(json));
        }
        input.value='';
    });

    const output=document.getElementById('output');
    ws.addEventListener('message', message=>{
        const json=JSON.parse(message.data);
        const line=document.createElement('div');

        if( json.clients!=null ){
            document.getElementById('n-client').innerHTML='只今、'+json.clients.length+'人が接続しています';
            if( json.method==='connect'    ){ line.innerHTML=json.user+'が接続しました'; }
            else if( json.method==='close' ){ line.innerHTML=json.user+'が接続を切りました'; }
            else console.log(不正なメソッドです, json.method);
            output.appendChild(line);
        }
        else if( json.message!=null ){
            line.innerHTML=json.user+' > '+json.message;
            output.appendChild(line);
        }
        else{ console.log('不正なデータ形式です'); }
    });
});
```
新たな接続があれば`open`イベントでサーバーに送っています。`close`はサーバー側で自動的に感知してくれるので書く必要はありません。
サーバーが送ったデータは`massage.data`に入っています。データによって出力側に色々書き出しています。

## 完成品
![image.png](https://qiita-image-store.s3.amazonaws.com/0/264735/3c85d51f-2e5d-4172-3686-5b8e4f0a7f4d.png)

デザインはかなり適当ですが...、こんな感じでチャットができます。

## あとがき
難しそうでしたがexpress-wsさえ使えば簡単にWebSocketが使えました、サーバーとクライアント側のデータの不一致でJavaScriptが死んだりはかなりしましたが...。
express-wsだとルーターの設定も簡単にできたので便利でした。
closeイベントも自動的に感知してくれたのでセッション管理などにも使えそうな気がしました。

[^ref]: [HerokuでExpressとexpress-wsを使ってチャットアプリを動かすサンプル](https://qiita.com/y-temp4/items/2a891772921db4361322)