1 min read
KI-EntwicklungSocket.IO für AI Chat Applications: Real-Time Implementation Guide
Production-ready AI Chat mit Socket.IO. Rooms, Typing Indicators, Message History und LLM-Streaming für skalierbare Chat-Anwendungen.
Socket.IOAI ChatReal-Time ChatNode.js ChatWebSocket ChatLLM Streaming

Socket.IO für AI Chat Applications: Real-Time Implementation Guide
Meta-Description: Production-ready AI Chat mit Socket.IO. Rooms, Typing Indicators, Message History und LLM-Streaming für skalierbare Chat-Anwendungen.
Keywords: Socket.IO, AI Chat, Real-Time Chat, Node.js Chat, WebSocket Chat, LLM Streaming, Chat Application
Einführung
Socket.IO ist der De-facto-Standard für Real-Time-Kommunikation in Node.js. Für AI-Chat-Anwendungen bietet es bidirektionale Events, automatische Reconnection und Room-basierte Isolation.
Architecture Overview
┌─────────────────────────────────────────────────────────────┐
│ AI CHAT WITH SOCKET.IO │
├─────────────────────────────────────────────────────────────┤
│ │
│ Client Server │
│ ┌─────────────┐ ┌─────────────────────┐ │
│ │ React │ socket.io │ Express + │ │
│ │ App │◄──────────────►│ Socket.IO │ │
│ └─────────────┘ └──────────┬──────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ LLM Service │ │
│ │ (Claude/GPT) │ │
│ └─────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Redis │ │
│ │ (Sessions/Cache) │ │
│ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘Server Implementation
Basic Setup
// src/server.ts
import express from 'express';
import { createServer } from 'http';
import { Server, Socket } from 'socket.io';
import { Redis } from 'ioredis';
import Anthropic from '@anthropic-ai/sdk';
const app = express();
const server = createServer(app);
const io = new Server(server, {
cors: {
origin: process.env.CLIENT_URL,
credentials: true
},
pingTimeout: 60000,
pingInterval: 25000
});
const redis = new Redis(process.env.REDIS_URL!);
const anthropic = new Anthropic();
// Middleware
io.use(async (socket, next) => {
const token = socket.handshake.auth.token;
try {
const user = await verifyToken(token);
socket.data.user = user;
next();
} catch (error) {
next(new Error('Authentication failed'));
}
});
// Connection Handler
io.on('connection', (socket: Socket) => {
const userId = socket.data.user.id;
console.log(`User connected: ${userId}`);
// Persönlicher Room für User
socket.join(`user:${userId}`);
setupChatHandlers(socket);
setupTypingHandlers(socket);
socket.on('disconnect', () => {
console.log(`User disconnected: ${userId}`);
});
});
server.listen(3000);Chat Event Handlers
// src/handlers/chat.ts
interface ChatMessage {
id: string;
conversationId: string;
role: 'user' | 'assistant';
content: string;
timestamp: Date;
}
function setupChatHandlers(socket: Socket) {
const userId = socket.data.user.id;
// Conversation beitreten
socket.on('join:conversation', async (conversationId: string) => {
// Berechtigung prüfen
const hasAccess = await checkConversationAccess(userId, conversationId);
if (!hasAccess) {
socket.emit('error', { message: 'Access denied' });
return;
}
socket.join(`conversation:${conversationId}`);
// History laden
const history = await loadConversationHistory(conversationId);
socket.emit('conversation:history', history);
});
// Neue Nachricht senden
socket.on('message:send', async (data: {
conversationId: string;
content: string;
}) => {
const { conversationId, content } = data;
// User-Nachricht speichern
const userMessage: ChatMessage = {
id: crypto.randomUUID(),
conversationId,
role: 'user',
content,
timestamp: new Date()
};
await saveMessage(userMessage);
// An alle in der Conversation senden
io.to(`conversation:${conversationId}`).emit('message:new', userMessage);
// AI Response generieren
await generateAIResponse(socket, conversationId, content);
});
// Conversation verlassen
socket.on('leave:conversation', (conversationId: string) => {
socket.leave(`conversation:${conversationId}`);
});
}AI Response mit Streaming
// src/handlers/ai-response.ts
async function generateAIResponse(
socket: Socket,
conversationId: string,
userMessage: string
) {
const responseId = crypto.randomUUID();
// Typing indicator starten
io.to(`conversation:${conversationId}`).emit('ai:typing', {
conversationId,
isTyping: true
});
try {
// Conversation History laden
const history = await loadConversationHistory(conversationId);
// Stream starten
const stream = await anthropic.messages.stream({
model: 'claude-3-haiku-20240307',
max_tokens: 1000,
system: 'Du bist ein hilfreicher Assistent.',
messages: history.map(m => ({
role: m.role,
content: m.content
}))
});
let fullContent = '';
// Token-by-Token streamen
for await (const event of stream) {
if (event.type === 'content_block_delta') {
const delta = event.delta.text;
fullContent += delta;
// Delta an Client senden
io.to(`conversation:${conversationId}`).emit('message:delta', {
messageId: responseId,
conversationId,
delta,
fullContent
});
}
}
// Vollständige Nachricht speichern
const assistantMessage: ChatMessage = {
id: responseId,
conversationId,
role: 'assistant',
content: fullContent,
timestamp: new Date()
};
await saveMessage(assistantMessage);
// Completion Event
io.to(`conversation:${conversationId}`).emit('message:complete', {
messageId: responseId,
conversationId
});
} catch (error) {
io.to(`conversation:${conversationId}`).emit('ai:error', {
conversationId,
error: 'AI response failed'
});
} finally {
// Typing indicator stoppen
io.to(`conversation:${conversationId}`).emit('ai:typing', {
conversationId,
isTyping: false
});
}
}Typing Indicators
// src/handlers/typing.ts
function setupTypingHandlers(socket: Socket) {
const userId = socket.data.user.id;
const typingTimeouts = new Map<string, NodeJS.Timeout>();
socket.on('typing:start', (conversationId: string) => {
// An andere User im Room senden
socket.to(`conversation:${conversationId}`).emit('user:typing', {
userId,
conversationId,
isTyping: true
});
// Auto-Stop nach 3 Sekunden
const existing = typingTimeouts.get(conversationId);
if (existing) clearTimeout(existing);
typingTimeouts.set(conversationId, setTimeout(() => {
socket.to(`conversation:${conversationId}`).emit('user:typing', {
userId,
conversationId,
isTyping: false
});
}, 3000));
});
socket.on('typing:stop', (conversationId: string) => {
const existing = typingTimeouts.get(conversationId);
if (existing) clearTimeout(existing);
socket.to(`conversation:${conversationId}`).emit('user:typing', {
userId,
conversationId,
isTyping: false
});
});
socket.on('disconnect', () => {
// Cleanup
typingTimeouts.forEach(timeout => clearTimeout(timeout));
});
}Client Implementation
React Hook
// src/hooks/useChat.ts
import { useEffect, useState, useCallback, useRef } from 'react';
import { io, Socket } from 'socket.io-client';
interface Message {
id: string;
role: 'user' | 'assistant';
content: string;
timestamp: Date;
isStreaming?: boolean;
}
export function useChat(conversationId: string) {
const socketRef = useRef<Socket | null>(null);
const [messages, setMessages] = useState<Message[]>([]);
const [isConnected, setIsConnected] = useState(false);
const [isAITyping, setIsAITyping] = useState(false);
const [streamingMessage, setStreamingMessage] = useState<string>('');
useEffect(() => {
// Socket Connection
const socket = io(process.env.NEXT_PUBLIC_WS_URL!, {
auth: { token: getAuthToken() },
transports: ['websocket']
});
socketRef.current = socket;
socket.on('connect', () => {
setIsConnected(true);
socket.emit('join:conversation', conversationId);
});
socket.on('disconnect', () => {
setIsConnected(false);
});
// Event Handlers
socket.on('conversation:history', (history: Message[]) => {
setMessages(history);
});
socket.on('message:new', (message: Message) => {
setMessages(prev => [...prev, message]);
});
socket.on('message:delta', ({ messageId, delta, fullContent }) => {
setStreamingMessage(fullContent);
});
socket.on('message:complete', ({ messageId }) => {
setMessages(prev => [
...prev,
{
id: messageId,
role: 'assistant',
content: streamingMessage,
timestamp: new Date()
}
]);
setStreamingMessage('');
});
socket.on('ai:typing', ({ isTyping }) => {
setIsAITyping(isTyping);
});
socket.on('ai:error', ({ error }) => {
console.error('AI Error:', error);
setIsAITyping(false);
});
return () => {
socket.emit('leave:conversation', conversationId);
socket.disconnect();
};
}, [conversationId]);
const sendMessage = useCallback((content: string) => {
if (socketRef.current) {
socketRef.current.emit('message:send', {
conversationId,
content
});
}
}, [conversationId]);
const startTyping = useCallback(() => {
socketRef.current?.emit('typing:start', conversationId);
}, [conversationId]);
const stopTyping = useCallback(() => {
socketRef.current?.emit('typing:stop', conversationId);
}, [conversationId]);
return {
messages,
streamingMessage,
isConnected,
isAITyping,
sendMessage,
startTyping,
stopTyping
};
}Chat Component
// src/components/Chat.tsx
import { useState, useRef, useEffect } from 'react';
import { useChat } from '../hooks/useChat';
export function Chat({ conversationId }: { conversationId: string }) {
const {
messages,
streamingMessage,
isConnected,
isAITyping,
sendMessage,
startTyping,
stopTyping
} = useChat(conversationId);
const [input, setInput] = useState('');
const messagesEndRef = useRef<HTMLDivElement>(null);
// Auto-scroll
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages, streamingMessage]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (input.trim()) {
sendMessage(input);
setInput('');
stopTyping();
}
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInput(e.target.value);
if (e.target.value) {
startTyping();
} else {
stopTyping();
}
};
return (
<div className="flex flex-col h-full">
{/* Connection Status */}
<div className={`px-4 py-2 text-sm ${
isConnected ? 'bg-green-100' : 'bg-red-100'
}`}>
{isConnected ? 'Connected' : 'Reconnecting...'}
</div>
{/* Messages */}
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map(message => (
<div
key={message.id}
className={`p-3 rounded-lg ${
message.role === 'user'
? 'bg-blue-100 ml-auto max-w-[80%]'
: 'bg-gray-100 mr-auto max-w-[80%]'
}`}
>
{message.content}
</div>
))}
{/* Streaming Message */}
{streamingMessage && (
<div className="bg-gray-100 mr-auto max-w-[80%] p-3 rounded-lg">
{streamingMessage}
<span className="animate-pulse">▋</span>
</div>
)}
{/* AI Typing Indicator */}
{isAITyping && !streamingMessage && (
<div className="bg-gray-100 mr-auto p-3 rounded-lg">
<span className="animate-pulse">● ● ●</span>
</div>
)}
<div ref={messagesEndRef} />
</div>
{/* Input */}
<form onSubmit={handleSubmit} className="p-4 border-t">
<div className="flex gap-2">
<input
type="text"
value={input}
onChange={handleInputChange}
placeholder="Nachricht eingeben..."
className="flex-1 px-4 py-2 border rounded-lg"
disabled={!isConnected}
/>
<button
type="submit"
disabled={!isConnected || !input.trim()}
className="px-4 py-2 bg-blue-500 text-white rounded-lg disabled:opacity-50"
>
Senden
</button>
</div>
</form>
</div>
);
}Scaling mit Redis Adapter
// src/server.ts
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';
const pubClient = createClient({ url: process.env.REDIS_URL });
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);
io.adapter(createAdapter(pubClient, subClient));
// Jetzt können mehrere Server-Instanzen kommunizierenBest Practices
1. Rate Limiting
import rateLimit from 'socket.io-rate-limiter';
io.use(rateLimit({
windowMs: 1000, // 1 Sekunde
max: 10 // Max 10 Events pro Sekunde
}));2. Input Validation
import { z } from 'zod';
const messageSchema = z.object({
conversationId: z.string().uuid(),
content: z.string().min(1).max(4000)
});
socket.on('message:send', async (data) => {
const result = messageSchema.safeParse(data);
if (!result.success) {
socket.emit('error', { message: 'Invalid message format' });
return;
}
// Process...
});3. Error Handling
socket.on('error', (error) => {
console.error('Socket error:', error);
socket.emit('error', { message: 'An error occurred' });
});
io.engine.on('connection_error', (error) => {
console.error('Connection error:', error);
});Fazit
Socket.IO für AI Chat bietet:
- Bidirektionale Kommunikation: Perfekt für Streaming-Responses
- Rooms: Isolation für Conversations
- Auto-Reconnection: Robuste Verbindung
- Skalierbarkeit: Redis Adapter für Multi-Server
Für produktionsreife AI-Chat-Anwendungen ist Socket.IO die pragmatische Wahl.
Bildprompts
- "Chat interface with AI assistant, real-time message bubbles appearing, modern app design"
- "WebSocket connection diagram between client and server, data flowing both ways"
- "Multiple users in chat room with AI, collaborative interface, friendly tech illustration"