【Socket.IO】Next.js + Node.js + Express で WebSocket通信 を試してみる

Next.js と Node.js で構成するWebアプリケーションにて、リアルタイムの双方向通信を行う必要があり、Socket.IO の利用を検討しています。
今回カウントアップアプリの作成を通じて、実際に Socket.IO を試してみたので、その内容をまとめます。


# 目次
* [1. はじめに](#1-はじめに)
* [2. クライアントサイドの実装](#2-クライアントサイドの実装)
    - [2-1. socket.io-clientのインストール](#2-1-socketio-clientのインストール)
    - [2-2. page.tsxの作成](#2-2-pagetsxの作成)
* [3. サーバサイドの実装](#3-サーバサイドの実装)
    - [3-1. socket.ioのインストール](#3-1-socketioのインストール)
    - [3-2. index.tsの作成](#3-2-indextsの作成)
* [4. Socket通信のテスト](#4-socket通信のテスト)
* [参考資料](#参考資料)


# 1. はじめに
今回は以下の構成で実施しました。

##### クライアントサイド
* next.js 15.2.1
* socket.io-client 4.8.1

##### サーバサイド
* node.js 22.13.1
* express 4.21.2
* socket.io 3.0.1

##### ディレクトリ

```
/
├── client
│   ├── src
│       ├── app
│           ├── layout.tsx
│           └── page.tsx
│   （※以下省略）
├── server
│   ├── dist
│   ├── src
│       └── index.ts
│   ├── .env
│   ├── package.json
│   ├── package-lock.json
│   ├── tsconfig.json
```


# 2. クライアントサイドの実装
クライアントサイドの実装は、`/client`の`page.tsx`にて行います。
今回クライアントサーバは、「`http://localhost:3000`」で起動します。


# 2-1. socket.io-clientのインストール
クライアント用の Socket.IO をインストールします。
`/client`ディレクトリ上で、以下のコマンドを実行します。

```bash:bash
npm install socket.io-client
```


# 2-2. page.tsxの作成
`page.tsx`では、クライアントサイドの Socket.IOイベント を定義します。
今回サーバサイドと通信する「`送信`」・「`受信`」のイベントは、それぞれ以下の名称で定義しています。
* sendToServer
* sentFromServer

```tsx: page.tsx
'use client';

import React, { useState } from 'react';
import { io } from 'socket.io-client';

const socket = io(
    'http://localhost:4000'
);

export default function Home() {
    const [currentNumber, setCurrentNumber] = useState(0);

    // サーバにリクエストを送信
    const sendToServer = () => {
        socket.emit('sendToServer', currentNumber);

        console.log('--------------------------------------------------');
        console.log('▼サーバにリクエストを送信しました。');
        console.log('> 現在の数値：', currentNumber);
        console.log('--------------------------------------------------');
    }

    // サーバからレスポンスを受信
    socket.on('sentFromServer', (responseNumber: number) => {
        setCurrentNumber(responseNumber);

        console.log('--------------------------------------------------');
        console.log('▼サーバからレスポンスを受信しました。');
        console.log('> 現在の数値：', responseNumber);
        console.log('--------------------------------------------------');
    });

    return (
        <div>
            <p>現在の数値：{currentNumber}</p>
            <button
                className="bg-blue-500 hover:bg-blue-400 text-white font-bold py-2 px-4 border-b-4 border-blue-700 hover:border-blue-500 rounded"
                onClick={sendToServer}
            >
                Count Up!
            </button>
        </div>
    );
};
```


# 3. サーバサイドの実装
サーバサイドの実装は、`/server`の`index.ts`にて行います。
今回バックエンドサーバは、「`http://localhost:4000`」で起動します。


# 3-1. socket.ioのインストール
サーバ用の Socket.IO をインストールします。
`/server`ディレクトリ上で、以下のコマンドを実行します。

```bash:bash
npm install socket.io
```


# 3-2. index.tsの作成
`index.ts`では、クライアントサイドと 同名のSocket.IOイベント を定義します。
今回はクライアントから受信した数値を、サーバサイドでカウントアップします。

```ts: index.ts
import cors from 'cors';
import dotenv from 'dotenv';
import express, { Request, Response } from 'express';
import { createServer } from 'http';
import { Socket } from 'socket.io';

dotenv.config();

const app = express();
const httpServer = createServer(app);

const io = require('socket.io')(httpServer, {
    cors: {
        origin: ['http://localhost:3000']
    },
});

const PORT = process.env.PORT;

app.use(cors());

app.get('/', (request: Request, response: Response) => {
    response.status(200).send('Hello World!!');
});

io.on('connection', (socket: Socket) => {
    console.log('--------------------------------------------------');
    console.log('▼Socketが接続されました。');
    console.log(`> ${socket.id}`);
    console.log('--------------------------------------------------');

    // 'sendToServer'イベントを受け取る
    socket.on('sendToServer', (requestNumber: number) => {
        const responseNumber = requestNumber + 1;
        socket.emit('sentFromServer', responseNumber);

        console.log('--------------------------------------------------');
        console.log('▼クライアントからリクエストを受信しました。');
        console.log('> リクエストの数値：', requestNumber);
        console.log('▼リクエストを処理しています。');
        console.log('> ', requestNumber, ' + 1');
        console.log('▼クライアントにレスポンスを送信しました。');
        console.log('> レスポンスの数値：', responseNumber);
        console.log('--------------------------------------------------');
    });
});

httpServer.listen(PORT, () => {
    console.log('Server running at PORT: ', PORT);
}).on('error', (error) => {
    throw new Error(error.message);
});
```

```plaintext: .env
PORT=4000
```


# 4. Socket通信のテスト
Socket通信のテストは、以下の手順で実行します。
##### 1. クライアントサーバを起動する。
`/client`ディレクトリ上で、以下のコマンドを実行します。
```bash:bash
npm run start
```

##### 2. バックエンドサーバを起動する。
はじめに`/server/src`ディレクトリ上で、以下のコマンドを実行します。
これにより、`index.ts`が`/dist`ディレクトリ上にコンパイルされます。
```bash:bash
npx tsc
```
次に`/server`ディレクトリ上で、以下のコマンドを実行します。
```bash:bash
npm run start
```

##### 3. ブラウザから「`http://localhost:3000`」にアクセスする。
Chrome等のWebブラウザから、クライアントサーバにアクセスします。

##### 4. カウントアップを実行する。
表示されている「`Count Up!`」ボタンをクリックすると、数値が1ずつ加算されていきます。
実行するとログ上から、Socket.IO を利用して通信できていることが確認できます。 
* サーバサイドのログ
![4.Socket通信のテスト_サーバサイドのログ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3527980/969ba99c-b04d-4edc-9962-d703ff8c3f6e.png)


# 参考資料
https://socket.io/docs/v4/client-api/

https://socket.io/docs/v4/server-api/