はじめに
こんにちは。
Webアプリ開発を学習中の初心者です。
双方向通信に興味があったため、その入り口としてシンプルなチャットアプリを作ってみました。
Socket.IOに興味がある初心者の方の参考になればと思います。
サンプルアプリ
使用技術
- フロントエンド: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公式ドキュメント
