0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React + Express + Socket.IOでシンプルなチャットアプリの開発

Last updated at Posted at 2025-07-18

はじめに

こんにちは。
Webアプリ開発を学習中の初心者です。
双方向通信に興味があったため、その入り口としてシンプルなチャットアプリを作ってみました。
Socket.IOに興味がある初心者の方の参考になればと思います。

サンプルアプリ

simple-chat.gif

使用技術

  • フロントエンド:React
  • バックエンド:Node.js (Express)
  • 型:TypeScript
  • 双方向通信:Socket.IO

セットアップ

フロントエンド

ルートディレクトリ
mkdir client
ルートディレクトリ
cd client
client
npm create vite@latest

React + typescriptを選択する。

以下をインストールする。

  • socket.io-client: ブラウザ側で動作するSocket.ioのクライアントライブラリ
client
npm install socket.io-client

バックエンド

ルートディレクトリ
mkdir server
ルートディレクトリ
cd server

package.jsonを作成。-yコマンドで質問をスキップする。

server
npm init -y

以下をインストールする。

  • express: サーバーとして使用する
  • typescript: Typescriptを使いたい場合はインストールする
  • @types/express: TypeScriptのExpressの型定義ファイル
  • socket.io: Node.js HTTPサーバーと統合するSocket.IOのサーバー
  • cors: CORSエラー対策
  • @types/node: TypeScriptのNode.jsのコアモジュールの型定義ファイル
  • ts-node-dev: TypeScriptを使用する場合にあったら便利。ts-node+ nodemonみたいなもの
server
npm install express typescript @types/express socket.io cors @types/cors @types/node ts-node-dev

実装

フロントエンド

今回はjoinしているRoomのチャットのみが表示されるようにしてみました。

client/App.tsx
import { useEffect, useState } from "react";
import io from "socket.io-client";

type MessageData = {
  message: string;
  roomNumber: string;
};

const socket = io("http://localhost:3000"); // サーバのURLを指定してsocketインスタンスを作成する

export default function App() {
  const [roomNumber, setRoomNumber] = useState("");

  const [message, setMessage] = useState("");
  const [messageReceived, setMessageReceived] = useState<Array<MessageData>>(
    []
  );

  const joinRoom = () => {
    if (!roomNumber) {
      return;
    }
    socket.emit("join_room", roomNumber);
  };

  const sendMessage = () => {
    socket.emit("send_message", { message, roomNumber });
    setMessage(""); // メッセージ送信後に入力フィールドをクリア
  };

  useEffect(() => {
    socket.on("receive_message", (data) => {
      setMessageReceived((prev) => [...prev, data]); // 受信したメッセージを状態に保存
    });

    return () => {
      socket.off("receive_message"); // コンポーネントのアンマウント時にイベントリスナーを解除
    };
  }, []);

  return (
    <div>
      <h1>シンプルなチャットアプリ</h1>
      <h2>Room Number:{roomNumber}</h2>
      <div>
        <input
          type="text"
          placeholder="Room Numberを入力"
          value={roomNumber}
          onChange={(e) => setRoomNumber(e.target.value)}
        />
        <button onClick={joinRoom}>Roomに参加する</button>
      </div>
      <input
        type="text"
        placeholder="messageを入力"
        value={message}
        onChange={(e) => setMessage(e.target.value)}
      />
      <button onClick={sendMessage}>送信</button>
      <h2>チャット欄</h2>
      <ul>
        {messageReceived
          .filter((msg) => msg.roomNumber === roomNumber)
          .map((msg, index) => {
            return <li key={index}>{msg.message}</li>;
          })}
      </ul>
    </div>
  );
}

バックエンド

server/index.ts
import express from "express";
import http from "http"; // socket.ioはhttpサーバ上で動作するため必要
import { Server } from "socket.io";
import cors from "cors";

const app = express();

app.use(cors()); // httpのcors設定

const server = http.createServer(app); // expressアプリケーションをhttpサーバーにラップ

const io = new Server(server, {
  cors: {
    origin: "http://localhost:5173", // フロントエンドのURLを指定、WebSocketのcors設定

  },
});

io.on("connection", (socket) => {
  socket.on("join_room", (roomNumber) => {
    // 既に他のroomにjoinしている場合は、既存のroomから離れる
    socket.rooms.forEach((room) => {
      if (room !== socket.id && room !== roomNumber) {
        socket.leave(room);
        console.log(`Socket ${socket.id} left room: ${room}`);
      }
    });

    // 新しいroomに参加する
    // 新しいroomに既にjoinしているかどうかで処理を分ける
    if (!socket.rooms.has(roomNumber)) {
      socket.join(roomNumber);
      console.log(`Socket ${socket.id} joined room: ${roomNumber}`);
    } else {
      console.log(`Socket ${socket.id} is already in room: ${roomNumber}`);
    }
  });

  socket.on("send_message", (data) => {
    io.to(data.roomNumber).emit("receive_message", data);
  });
});

server.listen(3000, () => {
  console.log("Server is running on http://localhost:3000");
});

おわりに

リアルタイム通信の実装を初めてやってみました。
動いているのを見るとすごく楽しいですね。
今後は認証機能やデータベースなども追加してみたいです。

参考資料

Socket.IO公式ドキュメント

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?