[JavaScript] Socket.ioを使ったリアルタイム通信アプリの作り方

はじめに

Socket.ioは、WebSocketに基づいた双方向通信を簡単に実現するライブラリです。

このチュートリアルでは、Node.jsとSocket.ioを使用して、リアルタイムチャットアプリケーション を作成する方法を詳しく解説します。

基本的な設定から、認証機能、接続管理、エラーハンドリング、スケーラビリティについても取り上げます。

必要な準備

以下のモジュールをインストールして、開発環境を整えましょう。

npm install express socket.io jsonwebtoken
  • express: Node.js用のWebフレームワーク。
  • socket.io: サーバーとクライアント間でリアルタイムな双方向通信を実現。
  • jsonwebtoken: JWT(JSON Web Token)を使用した簡単な認証システム。

サーバーサイドの実装

基本的なサーバー設定

まず、サーバー側で基本的なSocket.ioを利用したチャットのセットアップを行います。index.jsとして以下のコードを実装します。

// 必要なモジュールを読み込む
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const jwt = require('jsonwebtoken');

// Expressアプリケーションの作成
const app = express();
const server = http.createServer(app);

// Socket.ioの初期化
const io = socketIo(server);

// 静的ファイルを提供する
app.use(express.static('public'));

// ユーザー認証ミドルウェア
function authenticateJWT(req, res, next) {
  const token = req.headers['authorization'];
  if (token) {
    jwt.verify(token, 'secret_key', (err, user) => {
      if (err) {
        return res.sendStatus(403);
      }
      req.user = user;
      next();
    });
  } else {
    res.sendStatus(401);
  }
}

// クライアントからの接続を待機
io.on('connection', (socket) => {
  console.log('新しいクライアントが接続しました');

  // クライアントがメッセージを送信した時の処理
  socket.on('message', (data) => {
    console.log('受け取ったメッセージ:', data);
    // 他のクライアントにメッセージを送信
    io.emit('message', data);
  });

  // ユーザーが切断された時の処理
  socket.on('disconnect', () => {
    console.log('クライアントが切断されました');
  });
});

// サーバーを3000ポートで起動
server.listen(3000, () => {
  console.log('サーバーはポート3000で動作中');
});

エラーハンドリング

エラーハンドリングを追加することで、通信中にエラーが発生した場合でもアプリケーションが適切に動作し続けるようにします。例えば、接続の失敗や無効なメッセージの処理などです。

socket.on('error', (err) => {
  console.error('エラーが発生しました:', err);
  socket.emit('error', '通信エラーが発生しました。後ほど再試行してください。');
});

クライアントサイドの実装

クライアント側では、HTML、CSS、JavaScriptを使ってユーザーインターフェースを作成します。これに加えて、Socket.ioクライアントを通じてサーバーと通信します。

HTMLとCSSのセットアップ

public/index.htmlを作成します。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>リアルタイムチャット</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      margin: 20px;
    }
    #messages {
      border: 1px solid #ccc;
      padding: 10px;
      height: 200px;
      overflow-y: scroll;
      margin-bottom: 10px;
    }
  </style>
</head>
<body>
  <h1>リアルタイムチャット</h1>
  <div id="messages"></div>
  <input type="text" id="messageInput" placeholder="メッセージを入力">
  <button id="sendMessage">送信</button>

  <script src="/socket.io/socket.io.js"></script>
  <script>
    const socket = io();  // サーバーとの接続を確立

    // メッセージを受け取った時の処理
    socket.on('message', (data) => {
      const messagesDiv = document.getElementById('messages');
      const newMessage = document.createElement('div');
      newMessage.textContent = data;
      messagesDiv.appendChild(newMessage);
    });

    // 送信ボタンが押された時の処理
    document.getElementById('sendMessage').addEventListener('click', () => {
      const messageInput = document.getElementById('messageInput');
      const message = messageInput.value;
      if (message) {
        // サーバーにメッセージを送信
        socket.emit('message', message);
        messageInput.value = '';  // 入力フィールドをクリア
      }
    });
  </script>
</body>
</html>

JWT認証の追加

クライアントがメッセージを送信する前に、JWTを使って認証を行います。JWTを取得するために、最初にログイン機能を追加します。簡単な認証の流れを示します。

// クライアントでの認証(トークンをローカルストレージに保存)
localStorage.setItem('token', 'your-jwt-token');

// トークンをヘッダーに追加してリクエストを送信
fetch('/protected', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer ' + localStorage.getItem('token')
  }
}).then(response => response.json())
  .then(data => console.log(data));

スケーラビリティとパフォーマンス

Socket.ioは単一のサーバーで動作しますが、大規模なアプリケーションでは、複数のサーバー間で通信を同期するために、Redisクラスタリングを使用することが推奨されます。これにより、サーバーの負荷分散やメッセージの伝播がスムーズに行えます。

const io = require('socket.io')(server, {
  transports: ['websocket'],
  adapter: require('socket.io-redis')({
    host: 'localhost',
    port: 6379
  })
});

まとめ

このチュートリアルでは、Socket.ioを使ってリアルタイムチャットアプリケーションを実装しました。

基本的な通信の設定に加えて、認証、接続管理、エラーハンドリング、スケーラビリティに関する考慮点も紹介しました。

さらに、実践的なアプローチとして、JWTを用いたユーザー認証機能を追加する方法を説明しました。