Warum Socket.io 2026?
WebSockets sind Standard — aber Socket.io löst die echten Probleme: automatisches Reconnect, Room-Management, Namespace-Isolation und horizontales Scaling mit Redis. Claude Code generiert produktionsreife Socket.io-Architekturen inklusive TypeScript-Typen, Auth-Middleware und React-Hooks — in Minuten statt Stunden.
1. Socket.io Setup — io() Server, CORS, TypeScript-Typen
Der Einstieg in Socket.io beginnt mit dem Server-Setup. Claude Code richtet io() direkt auf einem bestehenden HTTP/Express-Server ein und konfiguriert CORS für Production-Umgebungen korrekt.
Installation und Basis-Setup
# Installation
npm install socket.io
npm install -D @types/socket.io typescript
// server.ts — io() auf Express-Server
import express from 'express';
import { createServer } from 'http';
import { Server, Socket } from 'socket.io';
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: {
origin: process.env.NODE_ENV === 'production'
? ['https://agentic-movers.com', 'https://app.agentic-movers.com']
: '*',
methods: ['GET', 'POST'],
credentials: true,
},
transports: ['websocket', 'polling'],
pingTimeout: 60000,
pingInterval: 25000,
});
TypeScript-Typen für Events
Claude Code generiert typsichere Event-Interfaces — kein any mehr in Socket-Callbacks:
// types/socket.ts — Event-Interfaces
interface ServerToClientEvents {
message: (data: { id: string; text: string; userId: string; ts: number }) => void;
userJoined: (data: { userId: string; room: string }) => void;
error: (err: { code: string; message: string }) => void;
}
interface ClientToServerEvents {
sendMessage: (payload: { room: string; text: string }, cb: (ack: AckResponse) => void) => void;
joinRoom: (room: string, cb: (ack: AckResponse) => void) => void;
}
interface AckResponse {
success: boolean;
error?: string;
}
type TypedServer = Server<ClientToServerEvents, ServerToClientEvents>;
type TypedSocket = Socket<ClientToServerEvents, ServerToClientEvents>;
Event-Handler registrieren
// io.on — Connection-Handler
io.on('connection', (socket: TypedSocket) => {
console.log(`Client connected: ${socket.id}`);
socket.on('sendMessage', async (payload, callback) => {
const message = { id: uuid(), ...payload, ts: Date.now() };
io.to(payload.room).emit('message', message);
callback({ success: true });
});
socket.on('disconnect', (reason) => {
console.log(`Disconnected: ${socket.id} — ${reason}`);
});
});
Prompt-Vorlage: "Erstelle einen typsicheren Socket.io-Server mit Express, CORS für Production und Event-Interfaces für ChatMessage, UserJoin und Error-Events." Claude Code generiert direkt die korrekten Generic-Parameter für Server<C, S>.
2. Rooms und Namespaces — Isolation und dynamische Gruppen
Rooms gruppieren Sockets logisch; Namespaces schaffen vollständig getrennte Kommunikationskanäle. Claude Code kombiniert beide Konzepte für Chat-Apps, Live-Dashboards und Multi-Tenant-Systeme.
socket.join / leave und io.to().emit
// Rooms — join, leave, broadcast
socket.on('joinRoom', async (room, callback) => {
try {
// Validierung: existiert der Room?
const allowed = await checkRoomAccess(socket.data.userId, room);
if (!allowed) {
return callback({ success: false, error: 'Access denied' });
}
await socket.join(room);
io.to(room).emit('userJoined', { userId: socket.data.userId, room });
callback({ success: true });
} catch (err) {
callback({ success: false, error: 'Server error' });
}
});
// Aus Room entfernen beim Disconnect
socket.on('disconnect', () => {
socket.rooms.forEach(async (room) => {
await socket.leave(room);
io.to(room).emit('userLeft', { userId: socket.data.userId });
});
});
Namespace-Isolation
// Separate Namespaces für verschiedene Features
const chatNs = io.of('/chat');
const dashboardNs = io.of('/dashboard');
const gameNs = io.of('/game');
chatNs.on('connection', (socket) => {
console.log(`Chat namespace: ${socket.id}`);
// Eigene Middleware, eigene Events, eigene Rooms
});
dashboardNs.on('connection', (socket) => {
// Pusht Live-Metriken — völlig isoliert von Chat-Events
const interval = setInterval(() => {
socket.emit('metrics', await getSystemMetrics());
}, 5000);
socket.on('disconnect', () => clearInterval(interval));
});
Dynamische Rooms für Multiplayer
// Game-Rooms dynamisch erstellen
const activeRooms = new Map<string, GameRoom>();
gameNs.on('connection', (socket) => {
socket.on('createRoom', (config: GameConfig, cb) => {
const roomId = generateRoomId();
activeRooms.set(roomId, { id: roomId, config: config, players: [socket.id] });
socket.join(roomId);
cb({ roomId });
});
socket.on('joinGame', (roomId: string, cb) => {
const room = activeRooms.get(roomId);
if (!room || room.players.length >= room.config.maxPlayers) {
return cb({ success: false, error: 'Room full or not found' });
}
room.players.push(socket.id);
socket.join(roomId);
gameNs.to(roomId).emit('playerJoined', { playerId: socket.id });
cb({ success: true });
});
});
Namespace = Feature-Trennung (Chat vs. Dashboard vs. Game). Room = Gruppen-Broadcast innerhalb eines Namespace. Claude Code erstellt beide Ebenen korrekt mit eigener Middleware-Chain pro Namespace.
3. Acknowledgements und Fehlerbehandlung
Socket.io Acknowledgements sind Callbacks, die der Server nach Event-Verarbeitung aufruft — ähnlich wie HTTP-Response-Status. Claude Code implementiert timeout-sichere Acks mit try/catch und Fehler-Propagation.
Server-seitige Ack mit Fehlerbehandlung
// Robuster sendMessage-Handler mit Ack
socket.on('sendMessage', async (payload, callback) => {
if (typeof callback !== 'function') return; // Guard: Ack vorhanden?
try {
// Validierung
if (!payload.text?.trim()) {
return callback({ success: false, error: 'Message cannot be empty' });
}
if (payload.text.length > 2000) {
return callback({ success: false, error: 'Message too long' });
}
// Persistierung
const saved = await db.saveMessage({
room: payload.room,
text: payload.text,
userId: socket.data.userId,
});
// Broadcast an Room
io.to(payload.room).emit('message', saved);
callback({ success: true, messageId: saved.id });
} catch (err) {
console.error('sendMessage error:', err);
callback({ success: false, error: 'Internal server error' });
}
});
Client-seitiges Emit mit Timeout
// Client: emit mit Timeout (Socket.io 4.4+)
try {
const response = await socket
.timeout(5000) // 5 Sekunden Timeout
.emitWithAck('sendMessage', { room: 'general', text: message });
if (!response.success) {
showError(response.error);
}
} catch (err) {
// Timeout oder Connection-Error
if (err.message.includes('timeout')) {
showError('Server antwortet nicht — bitte erneut versuchen');
} else {
showError('Verbindungsfehler');
}
}
Globaler Error-Handler
// Global error handler für alle Sockets
io.on('connection', (socket) => {
socket.use(([event, ...args], next) => {
const callback = args[args.length - 1];
if (typeof callback === 'function') {
const wrappedCallback = async (...cbArgs: unknown[]) => {
try {
await callback(...cbArgs);
} catch (err) {
socket.emit('error', { code: 'HANDLER_ERROR', message: 'Unhandled error' });
}
};
args[args.length - 1] = wrappedCallback;
}
next();
});
});
4. Redis Adapter — Horizontales Scaling mit mehreren Instanzen
Sobald deine App auf mehreren Node.js-Prozessen oder Servern läuft, müssen Events zwischen Instanzen synchronisiert werden. Der @socket.io/redis-adapter löst das Problem — Claude Code konfiguriert ihn mit ioredis für Production.
Ohne Redis Adapter erreichen Broadcasts von Instanz A nur Clients auf Instanz A — Clients auf Instanz B bekommen nichts. Bei einem einzelnen Prozess ist kein Adapter nötig.
Installation und Setup
# Redis Adapter + ioredis
npm install @socket.io/redis-adapter ioredis
// redis-adapter.ts
import { createAdapter } from '@socket.io/redis-adapter';
import { Redis } from 'ioredis';
const pubClient = new Redis({
host: process.env.REDIS_HOST || '127.0.0.1',
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD,
lazyConnect: true,
retryStrategy: (times) => Math.min(times * 100, 3000),
maxRetriesPerRequest: 3,
});
const subClient = pubClient.duplicate();
// Verbinden und Adapter setzen
await Promise.all([pubClient.connect(), subClient.connect()]);
io.adapter(createAdapter(pubClient, subClient));
console.log('Redis adapter connected ✓');
Redis Healthcheck und Fehlerbehandlung
// Redis connection error handling
pubClient.on('error', (err) => {
console.error('Redis pub error:', err);
// Graceful: Socket.io fällt auf lokalen Adapter zurück
});
subClient.on('error', (err) => {
console.error('Redis sub error:', err);
});
// Health-Endpoint für Load Balancer
app.get('/health', async (req, res) => {
const redisOk = pubClient.status === 'ready';
const sockets = await io.sockets().fetchSockets();
res.json({
status: 'ok',
redis: redisOk ? 'connected' : 'disconnected',
connections: sockets.length,
uptime: process.uptime(),
});
});
Multiple Instanzen mit PM2
// ecosystem.config.js — 4 Instanzen hinter Nginx
module.exports = {
apps: [{
name: 'socket-server',
script: 'dist/server.js',
instances: 4, // 4 CPU-Kerne
exec_mode: 'cluster',
env_production: {
NODE_ENV: 'production',
REDIS_HOST: '127.0.0.1',
REDIS_PORT: '6379',
},
}],
};
"Konfiguriere @socket.io/redis-adapter mit ioredis, Retry-Strategie, Health-Endpoint und PM2-Cluster-Config für 4 Instanzen." Claude Code generiert das komplette Setup inkl. Graceful Shutdown und Signal-Handling.
5. Auth-Middleware — JWT-Validierung und Connection-Security
Ohne Auth können beliebige Clients joinen. Claude Code implementiert socket.handshake.auth-basierte JWT-Validierung als Connection-Middleware — unsichere Verbindungen werden vor dem ersten Event abgelehnt.
JWT Auth-Middleware
// middleware/socketAuth.ts
import jwt from 'jsonwebtoken';
import { ExtendedError } from 'socket.io/dist/namespace';
interface JwtPayload {
userId: string;
email: string;
roles: string[];
exp: number;
}
export function socketAuthMiddleware(
socket: TypedSocket,
next: (err?: ExtendedError) => void
) {
const token = socket.handshake.auth.token
|| socket.handshake.headers.authorization?.replace('Bearer ', '');
if (!token) {
return next(new Error('Authentication required'));
}
try {
const payload = jwt.verify(token, process.env.JWT_SECRET!) as JwtPayload;
// User-Daten am Socket speichern
socket.data.userId = payload.userId;
socket.data.email = payload.email;
socket.data.roles = payload.roles;
next(); // Verbindung erlauben
} catch (err) {
if (err instanceof jwt.TokenExpiredError) {
return next(new Error('Token expired'));
}
return next(new Error('Invalid token'));
}
}
Middleware registrieren und Rate Limiting
// Middleware-Chain auf io und Namespaces
io.use(socketAuthMiddleware);
io.use(socketRateLimitMiddleware);
function socketRateLimitMiddleware(socket, next) {
const key = socket.handshake.address;
const count = rateLimitMap.get(key) || 0;
if (count >= 10) { // max 10 Verbindungen/IP/min
return next(new Error('Too many connections'));
}
rateLimitMap.set(key, count + 1);
setTimeout(() => rateLimitMap.set(key, (rateLimitMap.get(key) || 1) - 1), 60000);
next();
}
// Disconnect-Handling mit Cleanup
io.on('connection', (socket) => {
// socket.data.userId ist jetzt immer gesetzt
console.log(`Authenticated: ${socket.data.userId}`);
socket.on('disconnect', async (reason) => {
await updateUserPresence(socket.data.userId, 'offline');
await cleanupUserRooms(socket.data.userId);
});
});
JWT-Secret NIEMALS im Code — immer aus process.env.JWT_SECRET. Token-Ablauf validieren und Refresh-Token-Mechanismus getrennt von Socket-Events implementieren. Claude Code fügt diese Hinweise automatisch als Kommentare hinzu.
6. React-Client-Integration — useSocket-Hook und Reconnection
Claude Code generiert typsichere React-Hooks für Socket.io — mit korrektem useEffect cleanup, Reconnection-Handling und optimiertem Re-Render-Verhalten.
useSocket-Hook
// hooks/useSocket.ts
import { useEffect, useRef, useState, useCallback } from 'react';
import { io, Socket } from 'socket.io-client';
interface UseSocketOptions {
url: string;
namespace?: string;
token?: string;
autoConnect?: boolean;
}
export function useSocket({ url, namespace = '', token, autoConnect = true }: UseSocketOptions) {
const socketRef = useRef<Socket | null>(null);
const [connected, setConnected] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const socket = io(`${url}${namespace}`, {
auth: { token },
transports: ['websocket', 'polling'],
autoConnect,
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000,
reconnectionDelayMax: 10000,
});
socket.on('connect', () => { setConnected(true); setError(null); });
socket.on('disconnect', () => setConnected(false));
socket.on('connect_error', (err) => setError(err.message));
socketRef.current = socket;
// Cleanup: Socket beim Unmount disconnecten
return () => {
socket.disconnect();
socket.removeAllListeners();
socketRef.current = null;
};
}, [url, namespace, token]); // Nur neu verbinden wenn sich URL/Token ändert
const emit = useCallback(async <T>(event: string, data?: unknown): Promise<T> => {
if (!socketRef.current?.connected) throw new Error('Not connected');
return socketRef.current.timeout(5000).emitWithAck(event, data);
}, []);
return { socket: socketRef.current, connected, error, emit };
}
Chat-Component mit useSocket
// components/ChatRoom.tsx
export function ChatRoom({ room }: { room: string }) {
const { socket, connected, emit } = useSocket({
url: process.env.NEXT_PUBLIC_SOCKET_URL!,
namespace: '/chat',
token: useAuthToken(), // JWT aus Auth-Context
});
const [messages, setMessages] = useState<Message[]>([]);
const [text, setText] = useState('');
useEffect(() => {
if (!socket) return;
// Room joinen wenn connected
socket.emit('joinRoom', room, (ack) => {
if (!ack.success) console.error('Join failed:', ack.error);
});
const onMessage = (msg: Message) => {
setMessages((prev) => [...prev, msg]);
};
socket.on('message', onMessage);
return () => { socket.off('message', onMessage); }; // Cleanup!
}, [socket, room]);
const handleSend = async () => {
if (!text.trim()) return;
try {
await emit('sendMessage', { room, text });
setText('');
} catch {
alert('Senden fehlgeschlagen — bitte erneut versuchen');
}
};
return (
<div className="chat-container">
<div className="status">{connected ? '🟢 Verbunden' : '🔴 Getrennt'}</div>
<div className="messages">
{messages.map((m) => <div key={m.id}>{m.text}</div>)}
</div>
<input value={text} onChange={(e) => setText(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSend()} />
<button onClick={handleSend} disabled={!connected}>Senden</button>
</div>
);
}
Ohne socket.off('message', onMessage) im Cleanup registriert jedes Re-Render einen neuen Listener — das führt zu doppelten Nachrichten und Memory Leaks. Claude Code generiert immer korrekte Cleanup-Funktionen.
Reconnection-State-Management
// Reconnection-Events abfangen
useEffect(() => {
if (!socket) return;
socket.on('reconnect', (attempt) => {
console.log(`Reconnected after ${attempt} attempts`);
// Rooms erneut joinen nach Reconnect
socket.emit('joinRoom', room, () => {});
});
socket.on('reconnect_failed', () => {
setError('Verbindung konnte nicht wiederhergestellt werden');
});
return () => {
socket.off('reconnect');
socket.off('reconnect_failed');
};
}, [socket, room]);
Fazit: Socket.io mit Claude Code — Production-ready in Minuten
Socket.io + Claude Code ist ein starkes Duo für Echtzeit-Features. Die wichtigsten Punkte auf einen Blick:
- 1 TypeScript-first: Event-Interfaces mit ServerToClientEvents und ClientToServerEvents eliminieren Runtime-Fehler komplett.
- 2 Rooms + Namespaces: Rooms für Gruppen-Broadcasts, Namespaces für Feature-Isolation — beide zusammen für skalierbare Multi-Feature-Apps.
- 3 Acks mit Timeout: .timeout(5000).emitWithAck() (Socket.io 4.4+) macht asynchrone Kommunikation zuverlässig und fehlerbehandelt.
- 4 Redis Adapter: Ohne Redis kein Horizontal Scaling. Setup dauert 10 Minuten — Claude Code generiert das komplette ioredis-Setup mit Retry-Strategie.
- 5 Auth-Middleware: JWT-Validierung in io.use() vor dem ersten Event — jede Verbindung ist authentifiziert, kein zusätzlicher Auth-Event nötig.
- 6 React-Hooks: useSocket mit korrektem useEffect-Cleanup verhindert Memory Leaks und doppelte Event-Listener bei React-StrictMode.
Echtzeit-Apps mit KI-Support bauen?
Claude Code generiert typsichere Socket.io-Architekturen, Auth-Middleware und React-Hooks — starte deinen kostenlosen Trial und baue deine erste Echtzeit-App heute.
Jetzt kostenlos starten →