💬

【受験生が作る】リアルタイム性を重視した新感覚チャットアプリ「のーろぐちゃっと」 | Next.js + WebSocket

2023/01/11に公開4

きっかけ

受験勉強に疲れて開発に飢えていたその時、閃いてしまったのです、このアプリのアイデアを...!開発あんまめんどくなさそうだし作るっきゃねぇなという思いでサクっと作りました。

ちなみにというかタイトルに書いてますが自分は受験生です。この記事が公開される3日後ぐらいに共通テストを控えてます。ホントなにやってんでしょうね。

作ったもの

「のーろぐちゃっと」というリアルタイムチャットアプリを作りました!!

https://no-log-chat.vercel.app

普通のチャットアプリとは違ってログが直近3件までしか残らないという仕様で、リアルタイムさを追及した設計になっています。オープンチャットなので思いがけない人と会えるかも...?

技術的な話

技術の選定

サクっと作りたかったのでフロントエンドは自分が慣れてるNext.jsで作りました。ただUIは目新しいのが好きなのでMantine UIを使って、React Contextがちょい使いづらかったので状態管理にはJotaiを採用しました。

使ってみた感想ですが、JotaiはReduxを使うほどでもないけどReact Contextだと物足りない/使いづらいという人にかなりマッチするなと。こういうちょっとしたアプリには最適なんじゃないでしょうか。

Mantine UIは前からちょっと気になってたので初めて触れた~という感じです。見た目はイケイケで、開発体験も悪くない。コンポーネントも結構多い上にコンポーネントを利用したUIのテンプレが用意されてるのが便利✨(今回は使ってないけど) hooksも結構充実してて「実装できるけどちょっとめんどくさい」ぐらいのことが簡単にできたりします。

ESLintとかの設定をゼロからするのは時間とやる気と労力が足りないので大人しく先人のテンプレートリポジトリを借りました。はねつきとうかさんに感謝🙏

バックエンドはNext.jsのAPI Routesでやる予定だったんですが、WebSocketを扱えないため断念。Next.jsのカスタムサーバーを使えば実現できるんですが、Vercelでホスト出来なくなっちゃうので断念。結局Denoで書いてDeno Deployにホストするという形に落ち着きました。

WebSocketの難しさ

自分はWebSocketをほとんど使ったことがないので、何を使えばいいのかにまず悩みました。Socket.IOというWebSocketのラッパーライブラリを使おうかな~とも思ったんですが、そこまでめんどいことをしたいわけではないのでデフォルトのWebSocketを使うことにしました。

さらにNext.jsはコンポーネントをサーバー側で事前にレンダリングする関係上、useEffectの中にWebSocketの初期化処理を組み込まなきゃいけないのが少し手間でした。

参考までにWebSocket部分の実装をのっけておきます。省略した部分が多少あるのでご注意。

クライント側

import { useAtom } from 'jotai';
import { atom } from 'jotai';
import { useEffect } from 'react';

// WebSocketのオブジェクトを格納するatom
// この例だとstateでも可だが、他の場所でも使いたいのでatomにした
const socketAtom = atom<WebSocket | null>(null);

export const useChat = () => {
  const [socket, setSocket] = useAtom(socketAtom);

  useEffect(() => {
    setSocket((socket) => {
      // 既に入ってたら新しくsocketを作成しない
      // これをしないと2回以上接続するという変なことになってしまう
      if (socket) return socket;
      return new WebSocket(process.env.NEXT_PUBLIC_API_SERVER as string);
    });
  }, [setSocket]);

  useEffect(() => {
    if (socket) {
      // WebSocketからメッセージを受け取った時の処理
      socket.onmessage = (res) => {
        const payload = JSON.parse(res.data);
        
        // メッセージを受け取った時の処理
      };
    }
  }, [socket]);

  // メッセージを送信する処理
  const sendMessage = (message: string) => {
    socket?.send(
      // メッセージの内容
    );
  };

  return { sendMessage };
};

サーバー側

import { serve } from 'https://deno.land/std@0.156.0/http/server.ts';

// clientのWebSocketオブジェクトを格納しておくMap
const clients = new Map<number, WebSocket>();
let clientId = 0;

// client全員にメッセージを送る
const sendMessage = (message: string) => {
  clients.forEach((client) => {
    client.send(message);
  });
};

// WebSocketのハンドラー登録部分
const wsHandler = (ws: WebSocket) => {
  const id = ++clientId;
  clients.set(id, ws);
  // 接続時
  ws.onopen = () => {
    console.log('connected');
  };
  // メッセージ受信時
  ws.onmessage = (e) => {
    const payload = JSON.parse(e.data);
    sendMessage(e.data);
  };
  // 切断時
  ws.onclose = () => {
    clients.delete(id);
  };
};

serve(
  (req) => {
    // Deno.upgradeWebSocketでWebSocket用のサーバーになる
    // HTMLサーバーとWebSocketサーバーを兼ね備えるみたいなこともできるが、今回はフロントエンドのホストはVercelに任せているのでWebSocket専用サーバー
    const { response, socket } = Deno.upgradeWebSocket(req);

    // ハンドラー登録
    wsHandler(socket);

    return response;
  }
);

省略してないバージョンが見たい人はクライアント側サーバー側でそれぞれGitHubで公開してるので覗いてみてください。

反省

時間なさ過ぎてエラーハンドリングとかまともにやってないのが心残りですね。そのうち何とかするかもしれないししないかもしれない。

あと環境構築にちょい手間取っちゃったのも反省ですね。オレオレなテンプレートリポジトリが欲しいところです。

え?受験直前にアプリ開発して記事まで書いて反省してないのかって?してないよ

さいごに

気になった方は是非アプリを使ってみて欲しいです!

記事&ツイートの拡散もよろしくお願いします!!

https://twitter.com/__cp20__/status/1612963057055207425

GitHubで編集を提案

Discussion

てべすてんてべすてん

WebSocket(サーバ->クライアント)を気軽に使いたいなら Pusherというサービスがあるので、 共通テスト後に 見てみると面白いかもしれません。

https://pusher.com/

ただあまりReact向けの記事が充実していないのが現状で、個人的にいい感じの使い方を模索途中です。

しーぴーしーぴー

共通テスト後に見てみます!
情報ありがとうございます~🙏🙏