T-CREATOR

TypeScript で作る WebSocket 双方向通信アプリケーション

TypeScript で作る WebSocket 双方向通信アプリケーション

リアルタイムでメッセージが飛び交うチャットアプリケーション。そんな魅力的な機能を、TypeScript の型安全性を活かしながら構築してみませんか?

WebSocket を使った双方向通信は、現代の Web アプリケーションには欠かせない技術です。この記事では、実際に動作するチャットアプリケーションを作りながら、TypeScript での WebSocket 実装を深く学んでいきます。

初心者の方でも安心して取り組めるよう、段階的に進めていきましょう。きっと、あなたの開発スキルが一段階向上するはずです。

WebSocket の基礎知識

WebSocket プロトコルの仕組み

WebSocket は、HTTP とは異なる通信プロトコルです。一度接続を確立すると、サーバーとクライアントの間で双方向の通信が可能になります。

従来の HTTP 通信では、クライアントがリクエストを送信し、サーバーがレスポンスを返すという一方向の通信でした。しかし、WebSocket では接続が確立された後、どちらからでも自由にメッセージを送信できます。

makefileHTTP: クライアント → サーバー → クライアント(終了)
WebSocket: クライアント ↔ サーバー(継続的な接続)

この仕組みにより、リアルタイムチャットやライブ通知、オンラインゲームなど、即座に情報を共有する必要がある機能を実現できます。

HTTP との違いと利点

WebSocket と HTTP の主な違いを理解することで、適切な技術選択ができるようになります。

項目HTTPWebSocket
通信方式リクエスト・レスポンス双方向・継続的
接続毎回新規作成一度確立後維持
オーバーヘッドヘッダー情報が多い軽量
リアルタイム性低い高い
サーバー負荷高い低い

WebSocket の最大の利点は、リアルタイム性と効率性です。チャットアプリケーションでは、ユーザーがメッセージを送信した瞬間に他の参加者に届く必要があります。HTTP ではこれを実現するためにポーリングが必要でしたが、WebSocket では自然に実現できます。

双方向通信の必要性

現代の Web アプリケーションでは、双方向通信が当たり前になってきています。

例えば、チャットアプリケーションでは:

  • ユーザーがメッセージを送信
  • 他の参加者に即座に表示
  • 入力中の表示
  • 既読表示の更新

これらの機能は、すべて双方向通信が必要です。TypeScript の型システムを活用することで、この複雑な通信を安全に実装できます。

開発環境の準備

Node.js と TypeScript のセットアップ

まず、開発環境を整えましょう。Node.js と TypeScript がインストールされていることを確認します。

bash# Node.js のバージョン確認
node --version
npm --version

# TypeScript のグローバルインストール
yarn global add typescript

TypeScript の設定ファイルを作成します。これにより、型チェックやコンパイル設定を管理できます。

json:tsconfig.json{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

この設定により、最新の JavaScript 機能を活用しながら、厳密な型チェックが可能になります。

必要なパッケージのインストール

プロジェクトに必要なパッケージをインストールします。WebSocket サーバーとクライアントの両方に対応した構成にします。

bash# プロジェクトの初期化
yarn init -y

# サーバーサイド用パッケージ
yarn add express ws cors dotenv

# 開発用パッケージ
yarn add -D typescript @types/node @types/express @types/ws @types/cors nodemon ts-node

# クライアントサイド用パッケージ
yarn add react react-dom
yarn add -D @types/react @types/react-dom

package.json にスクリプトを追加して、開発を効率化します。

json:package.json{
  "scripts": {
    "dev": "nodemon --exec ts-node src/server.ts",
    "build": "tsc",
    "start": "node dist/server.js"
  }
}

プロジェクト構造の設計

効率的な開発のため、適切なディレクトリ構造を設計します。

javaproject/
├── src/
│   ├── server/
│   │   ├── server.ts
│   │   ├── websocket.ts
│   │   └── types.ts
│   ├── client/
│   │   ├── components/
│   │   ├── hooks/
│   │   └── types.ts
│   └── shared/
│       └── types.ts
├── public/
├── tsconfig.json
└── package.json

この構造により、サーバーとクライアントのコードを明確に分離し、共有の型定義を効率的に管理できます。

サーバーサイドの実装

WebSocket サーバーの作成

まず、基本的な WebSocket サーバーを作成します。Express.js と ws ライブラリを組み合わせて、堅牢なサーバーを構築しましょう。

typescript:src/server/types.ts// WebSocket メッセージの型定義
export interface ChatMessage {
  id: string;
  userId: string;
  username: string;
  content: string;
  timestamp: Date;
  type: 'message' | 'join' | 'leave';
}

// 接続中のユーザー情報
export interface ConnectedUser {
  id: string;
  username: string;
  ws: WebSocket;
}

// サーバーイベントの型定義
export interface ServerEvents {
  message: ChatMessage;
  userJoined: { userId: string; username: string };
  userLeft: { userId: string; username: string };
}

型定義を先に作成することで、開発中にエラーを早期発見できます。これが TypeScript の大きなメリットです。

typescript:src/server/websocket.tsimport { WebSocketServer, WebSocket } from 'ws';
import { ChatMessage, ConnectedUser, ServerEvents } from './types';

export class ChatWebSocketServer {
  private wss: WebSocketServer;
  private connectedUsers: Map<string, ConnectedUser> = new Map();

  constructor(server: any) {
    this.wss = new WebSocketServer({ server });
    this.initialize();
  }

  private initialize(): void {
    this.wss.on('connection', (ws: WebSocket) => {
      console.log('新しいクライアントが接続しました');

      ws.on('message', (data: string) => {
        try {
          const message = JSON.parse(data);
          this.handleMessage(ws, message);
        } catch (error) {
          console.error('メッセージの解析エラー:', error);
          this.sendError(ws, '無効なメッセージ形式です');
        }
      });

      ws.on('close', () => {
        this.handleDisconnect(ws);
      });
    });
  }
}

このコードでは、WebSocket サーバーの基本構造を定義しています。接続管理とメッセージハンドリングの基盤を作成しました。

接続管理とメッセージハンドリング

接続したユーザーを管理し、メッセージを適切に処理する機能を実装します。

typescript:src/server/websocket.ts  private handleMessage(ws: WebSocket, message: any): void {
    switch (message.type) {
      case 'join':
        this.handleUserJoin(ws, message);
        break;
      case 'message':
        this.handleChatMessage(ws, message);
        break;
      default:
        this.sendError(ws, '不明なメッセージタイプです');
    }
  }

  private handleUserJoin(ws: WebSocket, message: { username: string }): void {
    const userId = this.generateUserId();
    const user: ConnectedUser = {
      id: userId,
      username: message.username,
      ws
    };

    this.connectedUsers.set(userId, user);

    // 参加メッセージを全員に送信
    const joinMessage: ChatMessage = {
      id: this.generateMessageId(),
      userId,
      username: message.username,
      content: `${message.username} が参加しました`,
      timestamp: new Date(),
      type: 'join'
    };

    this.broadcastMessage(joinMessage);
    console.log(`${message.username} が参加しました (ID: ${userId})`);
  }

  private handleChatMessage(ws: WebSocket, message: { content: string }): void {
    const user = this.findUserByWebSocket(ws);
    if (!user) {
      this.sendError(ws, 'ユーザーが見つかりません');
      return;
    }

    const chatMessage: ChatMessage = {
      id: this.generateMessageId(),
      userId: user.id,
      username: user.username,
      content: message.content,
      timestamp: new Date(),
      type: 'message'
    };

    this.broadcastMessage(chatMessage);
  }

この実装により、ユーザーの参加・退出とメッセージ送信を適切に処理できます。型安全性により、予期しないデータ形式によるエラーを防げます。

TypeScript での型安全性の確保

型安全性を最大限に活用して、エラーを防ぐ仕組みを実装します。

typescript:src/server/websocket.ts  private broadcastMessage(message: ChatMessage): void {
    const messageStr = JSON.stringify(message);
    this.connectedUsers.forEach((user) => {
      if (user.ws.readyState === WebSocket.OPEN) {
        user.ws.send(messageStr);
      }
    });
  }

  private sendError(ws: WebSocket, errorMessage: string): void {
    const error = {
      type: 'error',
      message: errorMessage,
      timestamp: new Date()
    };

    if (ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify(error));
    }
  }

  private handleDisconnect(ws: WebSocket): void {
    const user = this.findUserByWebSocket(ws);
    if (user) {
      this.connectedUsers.delete(user.id);

      const leaveMessage: ChatMessage = {
        id: this.generateMessageId(),
        userId: user.id,
        username: user.username,
        content: `${user.username} が退出しました`,
        timestamp: new Date(),
        type: 'leave'
      };

      this.broadcastMessage(leaveMessage);
      console.log(`${user.username} が退出しました`);
    }
  }

  // ユーティリティメソッド
  private generateUserId(): string {
    return `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  private generateMessageId(): string {
    return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  private findUserByWebSocket(ws: WebSocket): ConnectedUser | undefined {
    return Array.from(this.connectedUsers.values()).find(user => user.ws === ws);
  }
}

型安全性により、コンパイル時に多くのエラーを発見できます。これにより、実行時の予期しないエラーを大幅に減らせます。

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

ブラウザでの WebSocket 接続

クライアントサイドでは、ブラウザの WebSocket API を使用してサーバーと通信します。TypeScript の型定義により、安全な実装が可能です。

typescript:src/client/types.ts// サーバーと共有する型定義
export interface ChatMessage {
  id: string;
  userId: string;
  username: string;
  content: string;
  timestamp: Date;
  type: 'message' | 'join' | 'leave';
}

// WebSocket 接続の状態
export type ConnectionStatus = 'connecting' | 'connected' | 'disconnected' | 'error';

// クライアント側のメッセージ型
export interface ClientMessage {
  type: 'join' | 'message';
  username?: string;
  content?: string;
}

型定義を共有することで、サーバーとクライアント間のデータ構造の一貫性を保てます。

typescript:src/client/hooks/useWebSocket.tsimport { useState, useEffect, useCallback, useRef } from 'react';
import { ChatMessage, ClientMessage, ConnectionStatus } from '../types';

interface UseWebSocketReturn {
  messages: ChatMessage[];
  connectionStatus: ConnectionStatus;
  sendMessage: (content: string) => void;
  joinChat: (username: string) => void;
  error: string | null;
}

export const useWebSocket = (url: string): UseWebSocketReturn => {
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>('disconnected');
  const [error, setError] = useState<string | null>(null);
  const wsRef = useRef<WebSocket | null>(null);

  const connect = useCallback(() => {
    try {
      const ws = new WebSocket(url);
      wsRef.current = ws;

      ws.onopen = () => {
        setConnectionStatus('connected');
        setError(null);
        console.log('WebSocket 接続が確立されました');
      };

      ws.onmessage = (event) => {
        try {
          const message: ChatMessage = JSON.parse(event.data);
          setMessages(prev => [...prev, message]);
        } catch (err) {
          console.error('メッセージの解析エラー:', err);
        }
      };

      ws.onclose = () => {
        setConnectionStatus('disconnected');
        console.log('WebSocket 接続が切断されました');
      };

      ws.onerror = (event) => {
        setConnectionStatus('error');
        setError('接続エラーが発生しました');
        console.error('WebSocket エラー:', event);
      };
    } catch (err) {
      setConnectionStatus('error');
      setError('接続の初期化に失敗しました');
    }
  }, [url]);

このカスタムフックにより、WebSocket 接続の複雑な処理を隠蔽し、React コンポーネントで簡単に使用できます。

React コンポーネントとの統合

React コンポーネントで WebSocket フックを使用し、ユーザーインターフェースを構築します。

typescript:src/client/hooks/useWebSocket.ts  const sendMessage = useCallback((content: string) => {
    if (wsRef.current?.readyState === WebSocket.OPEN) {
      const message: ClientMessage = {
        type: 'message',
        content
      };
      wsRef.current.send(JSON.stringify(message));
    }
  }, []);

  const joinChat = useCallback((username: string) => {
    if (wsRef.current?.readyState === WebSocket.OPEN) {
      const message: ClientMessage = {
        type: 'join',
        username
      };
      wsRef.current.send(JSON.stringify(message));
    }
  }, []);

  useEffect(() => {
    connect();
    return () => {
      wsRef.current?.close();
    };
  }, [connect]);

  return {
    messages,
    connectionStatus,
    sendMessage,
    joinChat,
    error
  };
};

このフックにより、コンポーネント側ではシンプルな API で WebSocket 機能を利用できます。

typescript:src/client/components/ChatApp.tsximport React, { useState } from 'react';
import { useWebSocket } from '../hooks/useWebSocket';
import { ChatMessage } from '../types';

const ChatApp: React.FC = () => {
  const [username, setUsername] = useState('');
  const [messageInput, setMessageInput] = useState('');
  const [isJoined, setIsJoined] = useState(false);

  const { messages, connectionStatus, sendMessage, joinChat, error } = useWebSocket(
    'ws://localhost:3001'
  );

  const handleJoin = () => {
    if (username.trim()) {
      joinChat(username);
      setIsJoined(true);
    }
  };

  const handleSendMessage = () => {
    if (messageInput.trim()) {
      sendMessage(messageInput);
      setMessageInput('');
    }
  };

  if (!isJoined) {
    return (
      <div className="join-form">
        <h2>チャットに参加</h2>
        <input
          type="text"
          placeholder="ユーザー名を入力"
          value={username}
          onChange={(e) => setUsername(e.target.value)}
        />
        <button onClick={handleJoin}>参加する</button>
      </div>
    );
  }

このコンポーネントでは、ユーザー名の入力からチャット参加、メッセージ送信まで一連の流れを実装しています。

リアルタイムメッセージ表示

受信したメッセージをリアルタイムで表示する機能を実装します。

typescript:src/client/components/ChatApp.tsx  return (
    <div className="chat-app">
      <div className="chat-header">
        <h2>リアルタイムチャット</h2>
        <div className={`status ${connectionStatus}`}>
          接続状態: {connectionStatus}
        </div>
      </div>

      {error && (
        <div className="error-message">
          エラー: {error}
        </div>
      )}

      <div className="messages-container">
        {messages.map((message: ChatMessage) => (
          <div key={message.id} className={`message ${message.type}`}>
            <div className="message-header">
              <span className="username">{message.username}</span>
              <span className="timestamp">
                {new Date(message.timestamp).toLocaleTimeString()}
              </span>
            </div>
            <div className="message-content">{message.content}</div>
          </div>
        ))}
      </div>

      <div className="message-input">
        <input
          type="text"
          placeholder="メッセージを入力..."
          value={messageInput}
          onChange={(e) => setMessageInput(e.target.value)}
          onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
        />
        <button onClick={handleSendMessage}>送信</button>
      </div>
    </div>
  );
};

export default ChatApp;

この実装により、ユーザーはリアルタイムでメッセージのやり取りを楽しめます。接続状態の表示により、ユーザーは現在の状況を把握できます。

エラーハンドリングと接続管理

接続断の検出と再接続

WebSocket 接続は不安定になることがあります。適切なエラーハンドリングと再接続機能を実装しましょう。

typescript:src/client/hooks/useWebSocket.tsimport { useState, useEffect, useCallback, useRef } from 'react';

interface ReconnectionConfig {
  maxAttempts: number;
  delay: number;
  backoffMultiplier: number;
}

export const useWebSocket = (
  url: string,
  config: ReconnectionConfig = { maxAttempts: 5, delay: 1000, backoffMultiplier: 2 }
) => {
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>('disconnected');
  const [error, setError] = useState<string | null>(null);
  const wsRef = useRef<WebSocket | null>(null);
  const reconnectAttemptsRef = useRef(0);
  const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);

  const attemptReconnect = useCallback(() => {
    if (reconnectAttemptsRef.current >= config.maxAttempts) {
      setConnectionStatus('error');
      setError('最大再接続回数に達しました');
      return;
    }

    const delay = config.delay * Math.pow(config.backoffMultiplier, reconnectAttemptsRef.current);

    reconnectTimeoutRef.current = setTimeout(() => {
      console.log(`再接続を試行中... (${reconnectAttemptsRef.current + 1}/${config.maxAttempts})`);
      connect();
      reconnectAttemptsRef.current++;
    }, delay);
  }, [config, connect]);

この実装により、接続が切断された場合に自動的に再接続を試行します。指数バックオフにより、サーバーに負荷をかけすぎないよう配慮しています。

typescript:src/client/hooks/useWebSocket.ts  const connect = useCallback(() => {
    try {
      setConnectionStatus('connecting');
      const ws = new WebSocket(url);
      wsRef.current = ws;

      ws.onopen = () => {
        setConnectionStatus('connected');
        setError(null);
        reconnectAttemptsRef.current = 0; // 接続成功時にリセット
        console.log('WebSocket 接続が確立されました');
      };

      ws.onclose = (event) => {
        setConnectionStatus('disconnected');
        console.log('WebSocket 接続が切断されました:', event.code, event.reason);

        // 正常な切断でない場合は再接続を試行
        if (event.code !== 1000) {
          attemptReconnect();
        }
      };

      ws.onerror = (event) => {
        setConnectionStatus('error');
        setError('接続エラーが発生しました');
        console.error('WebSocket エラー:', event);
      };

      // 既存の onmessage ハンドラー...
    } catch (err) {
      setConnectionStatus('error');
      setError('接続の初期化に失敗しました');
      attemptReconnect();
    }
  }, [url, attemptReconnect]);

エラー状態の適切な処理

様々なエラーケースに対応し、ユーザーに分かりやすいフィードバックを提供します。

typescript:src/client/components/ErrorBoundary.tsximport React, { Component, ErrorInfo, ReactNode } from 'react';

interface Props {
  children: ReactNode;
  fallback?: ReactNode;
}

interface State {
  hasError: boolean;
  error?: Error;
}

export class ErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    console.error('エラーバウンダリーでキャッチされたエラー:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div className="error-boundary">
          <h2>予期しないエラーが発生しました</h2>
          <p>ページを再読み込みしてください</p>
          <button onClick={() => window.location.reload()}>
            再読み込み
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

このエラーバウンダリーにより、予期しないエラーが発生してもアプリケーション全体がクラッシュすることを防げます。

ユーザビリティの向上

ユーザー体験を向上させるための機能を追加します。

typescript:src/client/components/ChatApp.tsximport React, { useState, useEffect, useRef } from 'react';

const ChatApp: React.FC = () => {
  const messagesEndRef = useRef<HTMLDivElement>(null);
  const [isTyping, setIsTyping] = useState(false);
  const [typingTimeout, setTypingTimeout] = useState<NodeJS.Timeout | null>(null);

  // メッセージが追加されたら自動スクロール
  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [messages]);

  // 入力中の表示
  const handleTyping = () => {
    setIsTyping(true);

    if (typingTimeout) {
      clearTimeout(typingTimeout);
    }

    const timeout = setTimeout(() => {
      setIsTyping(false);
    }, 1000);

    setTypingTimeout(timeout);
  };

  return (
    <div className="chat-app">
      {/* 既存のヘッダー部分 */}

      <div className="messages-container">
        {messages.map((message: ChatMessage) => (
          <div key={message.id} className={`message ${message.type}`}>
            {/* 既存のメッセージ表示 */}
          </div>
        ))}

        {isTyping && (
          <div className="typing-indicator">
            <span>入力中...</span>
          </div>
        )}

        <div ref={messagesEndRef} />
      </div>

      <div className="message-input">
        <input
          type="text"
          placeholder="メッセージを入力..."
          value={messageInput}
          onChange={(e) => {
            setMessageInput(e.target.value);
            handleTyping();
          }}
          onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
        />
        <button
          onClick={handleSendMessage}
          disabled={!messageInput.trim() || connectionStatus !== 'connected'}
        >
          送信
        </button>
      </div>
    </div>
  );
};

これらの機能により、ユーザーは快適にチャットを楽しめるようになります。

パフォーマンス最適化

メッセージの効率的な処理

大量のメッセージを効率的に処理するための最適化を実装します。

typescript:src/client/hooks/useWebSocket.tsimport { useState, useEffect, useCallback, useRef, useMemo } from 'react';

export const useWebSocket = (url: string) => {
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>('disconnected');
  const [error, setError] = useState<string | null>(null);
  const wsRef = useRef<WebSocket | null>(null);

  // メッセージの最大保持数
  const MAX_MESSAGES = 100;

  const addMessage = useCallback((message: ChatMessage) => {
    setMessages(prev => {
      const newMessages = [...prev, message];
      // 最大数を超えた場合は古いメッセージを削除
      if (newMessages.length > MAX_MESSAGES) {
        return newMessages.slice(-MAX_MESSAGES);
      }
      return newMessages;
    });
  }, []);

  // メッセージの重複チェック
  const isDuplicateMessage = useCallback((message: ChatMessage) => {
    return messages.some(msg => msg.id === message.id);
  }, [messages]);

  const handleMessage = useCallback((event: MessageEvent) => {
    try {
      const message: ChatMessage = JSON.parse(event.data);

      // 重複メッセージを防ぐ
      if (!isDuplicateMessage(message)) {
        addMessage(message);
      }
    } catch (err) {
      console.error('メッセージの解析エラー:', err);
    }
  }, [isDuplicateMessage, addMessage]);

この実装により、メモリ使用量を制御し、パフォーマンスを維持できます。

メモリリークの防止

React コンポーネントでのメモリリークを防ぐための対策を実装します。

typescript:src/client/hooks/useWebSocket.ts  useEffect(() => {
    connect();

    return () => {
      // クリーンアップ処理
      if (reconnectTimeoutRef.current) {
        clearTimeout(reconnectTimeoutRef.current);
      }

      if (wsRef.current) {
        wsRef.current.close(1000, 'コンポーネントのアンマウント');
      }
    };
  }, [connect]);

  // メッセージの定期クリーンアップ
  useEffect(() => {
    const cleanupInterval = setInterval(() => {
      setMessages(prev => {
        const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);
        return prev.filter(msg => new Date(msg.timestamp) > oneHourAgo);
      });
    }, 5 * 60 * 1000); // 5分ごとに実行

    return () => clearInterval(cleanupInterval);
  }, []);

これらの対策により、長時間の使用でもメモリリークが発生しません。

スケーラビリティの考慮

将来的な拡張を考慮した設計を実装します。

typescript:src/server/websocket.tsexport class ChatWebSocketServer {
  private wss: WebSocketServer;
  private connectedUsers: Map<string, ConnectedUser> = new Map();
  private rooms: Map<string, Set<string>> = new Map(); // ルーム機能の準備

  // メッセージキューの実装
  private messageQueue: ChatMessage[] = [];
  private readonly MAX_QUEUE_SIZE = 1000;

  private addToQueue(message: ChatMessage): void {
    this.messageQueue.push(message);
    if (this.messageQueue.length > this.MAX_QUEUE_SIZE) {
      this.messageQueue.shift(); // 古いメッセージを削除
    }
  }

  // パフォーマンス監視
  private logPerformanceMetrics(): void {
    const userCount = this.connectedUsers.size;
    const roomCount = this.rooms.size;
    const queueSize = this.messageQueue.length;

    console.log(`パフォーマンス指標: ユーザー数=${userCount}, ルーム数=${roomCount}, キューサイズ=${queueSize}`);
  }

  // 定期的なメトリクス出力
  private startMetricsLogging(): void {
    setInterval(() => {
      this.logPerformanceMetrics();
    }, 60000); // 1分ごと
  }
}

この設計により、ユーザー数が増えても安定して動作するシステムを構築できます。

まとめ

TypeScript と WebSocket を組み合わせた双方向通信アプリケーションの構築を通じて、多くの重要な概念を学びました。

型安全性の恩恵により、開発中に多くのエラーを早期発見でき、保守性の高いコードを書けるようになりました。WebSocket の特性を理解することで、リアルタイム性を必要とするアプリケーションの実装方法を習得できました。

この記事で学んだ技術は、チャットアプリケーション以外にも、オンラインゲーム、ライブ通知システム、コラボレーションツールなど、様々な分野で活用できます。

実際に手を動かしてコードを書くことで、理論だけでなく実践的なスキルも身についたはずです。この経験を活かして、さらに高度なアプリケーションの開発に挑戦してみてください。

技術の進歩は止まることを知りませんが、基礎をしっかりと身につけることで、新しい技術にも柔軟に対応できるようになります。この記事が、あなたの開発者としての成長の一助となれば幸いです。

関連リンク