Echtzeitfunktionen โ Live-Chat, Benachrichtigungen, gemeinsame Bearbeitung, Live-Dashboards โ erfordern eine dauerhafte Verbindung zwischen Client und Server. Im Jahr 2026WebSockets รผber Socket.io bleibt fรผr die meisten Echtzeitanwendungen die praktischste und am weitesten verbreitete Lรถsung. Dieses Tutorial baut ein vollstรคndiges Echtzeitsystem mit Skalierung รผber Redis Pub/Sub auf.
๐ Table of Contents
WebSockets vs. SSE vs. Long Polling
| Methode | Bidirektional | Latenz | Am besten fรผr |
|---|---|---|---|
| WebSockets | Ja | ~1ms | Chat, Spiele, Tools fรผr die Zusammenarbeit |
| Vom Server gesendete Ereignisse | Nein (nur Server โ Client) | ~5ms | Live-Dashboards, Benachrichtigungen |
| Lange Abfrage | Simuliert | 100-500 ms | Fallback fรผr eingeschrรคnkte Netzwerke |
Projekt-Setup
mkdir realtime-app && cd realtime-app
npm init -y
npm install express socket.io ioredis cors
# Frontend dependencies
npm install -D vite
Server: Express + Socket.io
// server.js
const express = require('express');
const { createServer } = require('http');
const { Server } = require('socket.io');
const Redis = require('ioredis');
const cors = require('cors');
const app = express();
const server = createServer(app);
const io = new Server(server, {
cors: {
origin: "http://localhost:5173", // Vite dev server
methods: ["GET", "POST"]
}
});
app.use(cors());
app.use(express.json());
// Redis Pub/Sub for multi-instance scaling
const pub = new Redis({ host: 'localhost', port: 6379 });
const sub = new Redis({ host: 'localhost', port: 6379 });
// โโ Active users tracking โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const activeUsers = new Map(); // socketId โ { userId, username, room }
// โโ Subscribe to Redis (messages from other server instances) โโ
sub.subscribe('chat', 'notifications', (err, count) => {
console.log(`Subscribed to ${count} Redis channels`);
});
sub.on('message', (channel, message) => {
const data = JSON.parse(message);
if (channel === 'chat') {
io.to(data.room).emit('message', data);
}
if (channel === 'notifications') {
io.to(data.userId).emit('notification', data);
}
});
// โโ Socket.io connection handler โโโโโโโโโโโโโโโโโโโโโโโโโโ
io.on('connection', (socket) => {
console.log(`Client connected: ${socket.id}`);
// โโ Authenticate โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
socket.on('authenticate', ({ userId, username }) => {
activeUsers.set(socket.id, { userId, username });
socket.join(userId); // join personal room for direct notifications
socket.emit('authenticated', { userId, socketId: socket.id });
console.log(`User authenticated: ${username} (${userId})`);
});
// โโ Join chat room โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
socket.on('join_room', ({ roomId }) => {
const user = activeUsers.get(socket.id);
if (!user) return socket.emit('error', 'Not authenticated');
socket.join(roomId);
activeUsers.set(socket.id, { ...user, room: roomId });
// Notify others in room
socket.to(roomId).emit('user_joined', {
userId: user.userId,
username: user.username,
roomId,
timestamp: Date.now()
});
console.log(`${user.username} joined room: ${roomId}`);
});
// โโ Send message โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
socket.on('send_message', async ({ roomId, content }) => {
const user = activeUsers.get(socket.id);
if (!user) return socket.emit('error', 'Not authenticated');
const message = {
id: `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`,
userId: user.userId,
username: user.username,
content,
room: roomId,
timestamp: Date.now()
};
// Publish to Redis so ALL server instances receive it
pub.publish('chat', JSON.stringify(message));
// TODO: persist to database here
// await db.messages.create({ data: message });
});
// โโ Typing indicator โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
socket.on('typing', ({ roomId, isTyping }) => {
const user = activeUsers.get(socket.id);
if (!user) return;
socket.to(roomId).emit('user_typing', {
userId: user.userId,
username: user.username,
isTyping
});
});
// โโ Disconnect โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
socket.on('disconnect', () => {
const user = activeUsers.get(socket.id);
if (user?.room) {
socket.to(user.room).emit('user_left', {
userId: user.userId,
username: user.username
});
}
activeUsers.delete(socket.id);
console.log(`Client disconnected: ${socket.id}`);
});
});
// โโ REST API: send notification from backend โโโโโโโโโโโโโโ
app.post('/notify', (req, res) => {
const { userId, type, message } = req.body;
pub.publish('notifications', JSON.stringify({ userId, type, message, timestamp: Date.now() }));
res.json({ ok: true });
});
server.listen(3001, () => console.log('Server running on port 3001'));
Client: React + Socket.io
// hooks/useSocket.ts
import { useEffect, useRef, useState } from 'react';
import { io, Socket } from 'socket.io-client';
export function useSocket(userId: string, username: string) {
const socket = useRef<Socket | null>(null);
const [connected, setConnected] = useState(false);
const [messages, setMessages] = useState<Message[]>([]);
const [notifications, setNotifications] = useState<Notification[]>([]);
const [typingUsers, setTypingUsers] = useState<Set<string>>(new Set());
useEffect(() => {
socket.current = io('http://localhost:3001');
socket.current.on('connect', () => {
setConnected(true);
socket.current!.emit('authenticate', { userId, username });
});
socket.current.on('message', (msg: Message) => {
setMessages(prev => [...prev, msg]);
});
socket.current.on('notification', (notif: Notification) => {
setNotifications(prev => [notif, ...prev]);
});
socket.current.on('user_typing', ({ username: typer, isTyping }) => {
setTypingUsers(prev => {
const next = new Set(prev);
isTyping ? next.add(typer) : next.delete(typer);
return next;
});
});
socket.current.on('disconnect', () => setConnected(false));
return () => { socket.current?.disconnect(); };
}, [userId, username]);
const joinRoom = (roomId: string) => socket.current?.emit('join_room', { roomId });
const sendMessage = (roomId: string, content: string) =>
socket.current?.emit('send_message', { roomId, content });
const setTyping = (roomId: string, isTyping: boolean) =>
socket.current?.emit('typing', { roomId, isTyping });
return { connected, messages, notifications, typingUsers, joinRoom, sendMessage, setTyping };
}
Skalierung mit Redis Pub/Sub
Das Redis Pub/Sub-Muster im Servercode ermรถglicht eine horizontale Skalierung: Wenn eine Nachricht auf einer Node.js-Instanz eintrifft, wird sie auf Redis verรถffentlicht. Alle anderen Instanzen (bei Redis abonniert) empfangen es und senden es an ihre verbundenen Clients. Das bedeutet, dass Sie 10 Node.js-Instanzen hinter einem Load Balancer ausfรผhren kรถnnen und Nachrichten alle Benutzer erreichen, unabhรคngig davon, mit welcher Instanz sie verbunden sind.
# Run multiple instances
PORT=3001 node server.js &
PORT=3002 node server.js &
PORT=3003 node server.js &
# Nginx load balancer with sticky sessions (WebSocket requirement)
upstream websocket {
ip_hash; # sticky sessions - same client hits same server
server localhost:3001;
server localhost:3002;
server localhost:3003;
}
Hรคufig gestellte Fragen
F: Socket.io vs. native WebSocket-API?
A: Socket.io fรผgt hinzu: automatische Wiederverbindung, Rรคume/Namespaces, Fallback auf Long-Polling und ereignisbasierte API. Native WebSocket ist eine niedrigere Ebene. Verwenden Sie Socket.io fรผr die Produktion โ allein die Wiederverbindungslogik spart Stunden Arbeit.
F: Wie viele gleichzeitige WebSocket-Verbindungen kann Node.js verarbeiten?
A: Auf einem einzelnen 4-Core-Server mit 8 GB RAM kรถnnen Sie je nach Nachrichtenhรคufigkeit mit 10.000 bis 50.000 gleichzeitigen Verbindungen rechnen. Socket.io mit Redis-Clustering skaliert auf Millionen รผber mehrere Server hinweg.
F: Funktionieren WebSockets รผber Firewalls und Proxys hinweg?
A: Die meisten modernen Netzwerke erlauben WebSockets auf Port 443 (HTTPS/WSS). Socket.io greift auf Long-Polling zurรผck, wenn WebSocket blockiert ist. Verwenden Sie WSS (sicheres WebSocket) in der Produktion.
F: Wie kann ich Chatnachrichten dauerhaft speichern?
A: In PostgreSQL oder MongoDB speichern. Index nach (roomId, Zeitstempel) fรผr schnelles Abrufen. Laden Sie die letzten 50 Nachrichten, wenn ein Benutzer einem Raum รผber die REST-API beitritt; Streamen Sie neue Nachrichten รผber WebSocket.
F: Gibt es eine verwaltete WebSocket-Dienstalternative?
A: Pusher, Ably und AWS API Gateway WebSocket sind verwaltete Alternativen. Hรถhere Kosten, aber kein Infrastrukturmanagement. Gut fรผr kleine Mengen oder wenn die Entwicklerzeit ein Engpass ist.
Fazit
WebSockets mit Socket.io und Redis Pub/Sub ist die bewรคhrte, skalierbare Architektur fรผr Echtzeitanwendungen im Jahr 2026. Der Servercode in diesem Tutorial verwaltet Tausende gleichzeitiger Benutzer auf einer einzigen Instanz und skaliert bei steigendem Bedarf horizontal รผber Redis. Beginnen Sie mit einer einzelnen Instanz, fรผgen Sie Redis hinzu, wenn Sie รผber einen Server hinaus skalieren mรผssen, und wechseln Sie nur dann zu verwalteten WebSocket-Diensten, wenn der Betriebsaufwand zum Problem wird.
๐ Share this article
โ๏ธ Leave a Comment