[Next.js+Node.js+Socket.io]新体操の採点システムを作った話

# はじめに

新体操の採点をすることができるWebアプリの作成をしました。
Node.jsやMongoDBをまともに触るのは初めてだったので、少し苦戦しました。
今回は、システムの説明と詰まったところや反省点について説明していきたいと思います。


使用技術
- Next.js(Typescript)
- Node.js(Express.js + Mongoose)
- WebSockt(Socket.io)
- MongoDB

# GitHubのリポジトリ

フロントエンドのリポジトリ↓
https://github.com/mrkmtkm/mrg-referee-front

バックエンドのリポジトリ↓
https://github.com/mrkmtkm/mrg-referee-backend


# 概要
ユーザーは、大会の主催者と審判の２人がいます。
簡単に説明すると、審判の採点した点数を主催者側でリアルタイムで受け取り、点数を集計し表示するシステムです。
実際はこんな感じです。

https://youtu.be/YbKGsJ39jd4

特徴としては
- 手軽で簡単に使えるようにする(審判は大会のIDがわかれば採点できるようにした)

- リアルタイムで採点される
という感じなのですが、これと言ってなかったので、なんとか絞り出しました、、、

# 技術選定
前提として、審判の採点した点数を主催者側でリアルタイムで受け取り、点数を集計し表示するシステムを作るのが目的でした。

技術選定の流れ
1. リアルタイム通信をするためのプロトコルとして、通信コストの低いWebSocketを使用しました。
2. WebSocketを使用するために、リアルタイムな処理が早いNode.jsを使用しました。
3. DBは読み書きが高速で、Node.jsとの相性がいいMongoDBを使用しました。
4. フロントエンドは使い慣れていて、ルーティングなどが楽なNext.jsを使用しました。

# 詰まったところと反省点
## ExpressでのリクエストボディのJSONの受け取り
 Expressで通常の状態では、JSONをリクエストボディで受け取れないことがわからず、
 気づくまでに時間がかかりました、、
 以下のようにすることにより、JSONを受け取れるようになります。

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

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
```


## Mogooseでの更新処理
Resultモデルのdeductionはnullを許容していて、ドキュメントの登録の時にはnullで登録されるため、MongoDB上のドキュメントにはdeductionのフィールドがない状況でした。

以下のsample1.jsのように、
モデル.フィールド名=変更したい値
としてしまうと、既存のドキュメントにあったフィールドの更新はされますが、その時点でドキュメントになかったdeductionフィールドは更新されませんでした。

なので調べてみると、以下のsample2.jsのように、
findOneAndUpdateというメソッドを見つけ、それを使用したら、問題が解決しました。

```js:sample1.js
 const result= await Result.findOne( { _id: req.body.id });
    result.execution=req.body.execution;
    result.difficulty=req.body.difficulty;
    result.deduction=req.body.deduction;
    const saveResult = await result.save;
```


```js:sample2.js
const result = await Result.findOneAndUpdate(
      { _id: req.body.id },
      {
        $set: {
          execution: req.body.execution,
          difficulty: req.body.difficulty,
          deduction: req.body.deduction,
        },
      }
    );
```

## 採点データのやり取り
〜前提〜
審判が採点する前後の動きは以下のようになっています。
1. 主催者が審判に採点する選手のデータを送る
2. データを受け取った審判が採点して、サーバーに送る
3. サーバーでデータを保存
4. 主催者側はuseEffectで５秒ごとに採点データを取りにいき、そのデータで集計する
-補足-
審判はサイトをリロードすると採点する選手のデータが消える(Socket.ioでの通信が切れるため)
主催者側は審判からの選手の採点データが消えないようにuseEffectを使用

-本題-
主催者側で５秒ごとに採点データ取得しに行くのは、以下のsample.jsのようになっていて、
useEffectとsetTimeoutを使用して実装しています。
ですが、たまにサーバーとのやりとりが多過ぎて、Runtime Errorが発生してしまうことがあるところがこの実装の懸念点です。
なので、審判側も主催者側もデータを一時的にデータを保存しておけば、useEffectでいちいちサーバーのAPIを叩く必要がなかったと思いました。(Redux使うのがいいのかな？)

-疑問-
Socket.ioで送られてきたデータを逐一Cookieに保存すると、フロントの速度が大丈夫なのかと思ってしましました。
**Socket.ioを使用したフロントの状態管理でもっといいやり方があったら教えていただきたいです。**

```js:sample.js
  const [result, setResult] = useState<Result>();
  const [listData, setListData] = useState<RefereeScore[]>([]);
  const [count, setCount] = useState(0);
  
useEffect(() => {
    if (result) {
      RefereeScoreRepository.index(resultId).then((data) => {
        setListData(data);
        const difficulty = data.map((d) => d.difficulty);
        const execution = data.map((d) => d.execution);
        const len = data.length;
        let totalDifficulty = difficulty.reduce(
          (sum, element) => sum + element,
          0
        );
        let totalExecution = execution.reduce(
          (sum, element) => sum + element,
          0
        );

        setResult({
          id: result.id,
          tournamentId: result.tournamentId,
          playerName: result.playerName,
          item: result.item,
          execution: totalExecution / len,
          difficulty: totalDifficulty / len,
          deduction: result.deduction,
        });
      });
    }
    setTimeout(function () {
      setCount(count + 1);
    }, 5000);
  }, [count, isGetResult]);
```

## ビジョンを明確にすることの大切さ、設計能力の低さを実感
つくりたいものの明確なビジョンや仕様、期限などをあまり決めずに開発をおこなってしまったため、ダラダラと開発をしてしまった結果１ヶ月もかかってしましました。
なので、次に個人開発をするときは以下のことを注意していきたいと思います。

- 期限を決める
- 仕事と新体操の練習でいくらやる気が無かろうと、５秒だけでも開発をする
- 最初にある程度自分がそのアプリをどのように使うかのイメージを明確にする

# 終わりに
今回はWebアプリは作ったものの、やる気が起きずデプロイまでするのはやめました、、
次回のものはデプロイまで頑張ります、、
一応、２週間から３週間の間で１アプリをリリースするのを目標にやっていたのですが、
割と初めの方でその目標が達成できなかったので、次こそはできるようにしていきたいです。

あと、デザインセンスが全くないので、どうしたら簡単にいいデザインになるかを押しし得ていただきたいです。
指摘点などありましたらコメントしていただけると幸いです。

次は初めてのスマホアプリの作成を頑張ってみたいと思います。