Use Cases

Chat WebSocket avec Claude Code

Découvrez chat WebSocket avec Claude Code. Conseils pratiques et exemples de code inclus.

WebSocketチャットアプリをClaude Codeで作る

リアルタイムチャットアプリはWebSocketの代表的なユースケースです。Claude Codeを使えば、認証付きのチャットルーム、メッセージ履歴、オンラインステータスまで含めた本格的なチャットアプリを効率的に構築できます。

プロジェクトの初期セットアップ

> WebSocketチャットアプリを作りたい。
> Node.js + Express + Socket.IO でサーバーを、
> React + TypeScript でフロントエンドを構築して。
> ルーム機能とメッセージ履歴を実装して。

Claude Codeはこのプロンプトから、サーバーとクライアントの両方を一貫したアーキテクチャで生成してくれます。

サーバーサイドの実装

// server/src/chat-server.ts
import { Server, Socket } from 'socket.io';
import { createServer } from 'http';
import express from 'express';

interface Message {
  id: string;
  roomId: string;
  sender: string;
  content: string;
  timestamp: number;
}

const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
  cors: { origin: process.env.CLIENT_URL || 'http://localhost:3000' },
});

// メッセージ履歴をメモリに保持(本番ではDBを使用)
const messageHistory = new Map<string, Message[]>();

io.use((socket, next) => {
  const token = socket.handshake.auth.token;
  if (!token) return next(new Error('認証が必要です'));
  // トークン検証ロジック
  next();
});

io.on('connection', (socket: Socket) => {
  const username = socket.handshake.auth.username;

  socket.on('join-room', (roomId: string) => {
    socket.join(roomId);
    // 過去のメッセージを送信
    const history = messageHistory.get(roomId) || [];
    socket.emit('message-history', history.slice(-50));
    io.to(roomId).emit('user-status', { username, status: 'online' });
  });

  socket.on('send-message', (data: { roomId: string; content: string }) => {
    const message: Message = {
      id: crypto.randomUUID(),
      roomId: data.roomId,
      sender: username,
      content: data.content,
      timestamp: Date.now(),
    };
    if (!messageHistory.has(data.roomId)) {
      messageHistory.set(data.roomId, []);
    }
    messageHistory.get(data.roomId)!.push(message);
    io.to(data.roomId).emit('new-message', message);
  });

  socket.on('disconnect', () => {
    io.emit('user-status', { username, status: 'offline' });
  });
});

httpServer.listen(4000, () => console.log('Chat server running on :4000'));

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

// client/src/hooks/useChat.ts
import { useEffect, useState, useCallback } from 'react';
import { io, Socket } from 'socket.io-client';

interface Message {
  id: string;
  roomId: string;
  sender: string;
  content: string;
  timestamp: number;
}

export function useChat(roomId: string, token: string, username: string) {
  const [messages, setMessages] = useState<Message[]>([]);
  const [socket, setSocket] = useState<Socket | null>(null);
  const [isConnected, setIsConnected] = useState(false);

  useEffect(() => {
    const s = io('http://localhost:4000', {
      auth: { token, username },
    });

    s.on('connect', () => {
      setIsConnected(true);
      s.emit('join-room', roomId);
    });

    s.on('message-history', (history: Message[]) => {
      setMessages(history);
    });

    s.on('new-message', (message: Message) => {
      setMessages((prev) => [...prev, message]);
    });

    s.on('disconnect', () => setIsConnected(false));

    setSocket(s);
    return () => { s.disconnect(); };
  }, [roomId, token, username]);

  const sendMessage = useCallback((content: string) => {
    socket?.emit('send-message', { roomId, content });
  }, [socket, roomId]);

  return { messages, sendMessage, isConnected };
}

メッセージコンポーネント

// client/src/components/ChatRoom.tsx
import { useChat } from '../hooks/useChat';
import { useState } from 'react';

export function ChatRoom({ roomId, token, username }: {
  roomId: string;
  token: string;
  username: string;
}) {
  const { messages, sendMessage, isConnected } = useChat(roomId, token, username);
  const [input, setInput] = useState('');

  const handleSend = () => {
    if (!input.trim()) return;
    sendMessage(input);
    setInput('');
  };

  return (
    <div className="flex flex-col h-screen">
      <div className="p-4 border-b flex justify-between">
        <h2>Room: {roomId}</h2>
        <span className={isConnected ? 'text-green-500' : 'text-red-500'}>
          {isConnected ? '接続中' : '切断'}
        </span>
      </div>
      <div className="flex-1 overflow-y-auto p-4 space-y-2">
        {messages.map((msg) => (
          <div key={msg.id} className={msg.sender === username ? 'text-right' : ''}>
            <span className="text-sm text-gray-500">{msg.sender}</span>
            <p className="bg-gray-100 rounded-lg p-2 inline-block">{msg.content}</p>
          </div>
        ))}
      </div>
      <div className="p-4 border-t flex gap-2">
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyDown={(e) => e.key === 'Enter' && handleSend()}
          className="flex-1 border rounded px-3 py-2"
          placeholder="Type a message..."
        />
        <button onClick={handleSend} className="bg-blue-500 text-white px-4 rounded">
          送信
        </button>
      </div>
    </div>
  );
}

本番環境での注意点

Claude Codeに以下の点を追加で依頼すると、本番品質に近づけられます。

  • メッセージの永続化: RedisやPostgreSQLにメッセージを保存する
  • レート制限: スパム防止のためのメッセージ送信制限
  • XSS対策: ユーザー入力のサニタイズ処理
  • 再接続ロジック: 切断時の自動再接続とメッセージ再取得

関連リソース

WebSocketの基礎的な実装パターンについてはWebSocket/リアルタイム通信の実装ガイドも参考にしてください。認証周りの実装は認証機能の実装で詳しく解説しています。また、チャットボットとの統合を考えている場合はチャットボット開発もご覧ください。

Socket.IOの公式ドキュメント(socket.io/docs)も併せて確認するとよいでしょう。

#Claude Code #WebSocket #チャット #real-time #Node.js