Back to Blog
Real-TimeWebSocketsSSETutorial

Building Real-Time Features with AI Assistance

Master real-time application development with WebSockets, Server-Sent Events, and modern patterns, guided by AI.

B
Bootspring Team
Engineering
September 12, 2025
6 min read

Real-time features transform static applications into dynamic experiences. Live notifications, collaborative editing, real-time dashboards—users expect instant updates. AI can help you implement these features correctly from the start.

Choosing the Right Technology#

WebSockets#

Full-duplex communication for bidirectional, low-latency needs:

1// Client 2const ws = new WebSocket('wss://api.example.com/ws'); 3 4ws.onmessage = (event) => { 5 const data = JSON.parse(event.data); 6 handleUpdate(data); 7}; 8 9ws.send(JSON.stringify({ type: 'subscribe', channel: 'updates' }));

Best for: Chat, gaming, collaborative editing, trading platforms

Server-Sent Events (SSE)#

One-way server-to-client streaming:

1// Client 2const eventSource = new EventSource('/api/events'); 3 4eventSource.onmessage = (event) => { 5 const data = JSON.parse(event.data); 6 handleUpdate(data); 7}; 8 9// Server 10app.get('/api/events', (req, res) => { 11 res.setHeader('Content-Type', 'text/event-stream'); 12 res.setHeader('Cache-Control', 'no-cache'); 13 14 const interval = setInterval(() => { 15 res.write(`data: ${JSON.stringify(getUpdates())}\n\n`); 16 }, 1000); 17 18 req.on('close', () => clearInterval(interval)); 19});

Best for: Notifications, feeds, dashboards, progress updates

Long Polling#

Fallback for restricted environments:

1async function poll() { 2 try { 3 const response = await fetch('/api/updates?timeout=30'); 4 const data = await response.json(); 5 handleUpdate(data); 6 } finally { 7 poll(); // Immediately reconnect 8 } 9}

Best for: When WebSockets and SSE aren't available

Pattern 1: Real-Time Notifications#

Design a notification system with these requirements: Features: - Push notifications to online users - Persist unread notifications for offline users - Support notification types: mention, like, follow, system - Allow muting/preferences per type Tech stack: - Node.js backend - Redis for pub/sub - PostgreSQL for persistence - React frontend Include: - WebSocket server implementation - Notification service - Frontend hook for consuming notifications

Pattern 2: Live Collaboration#

Implement collaborative text editing: Requirements: - Multiple users edit same document - Real-time cursor positions - Conflict resolution for simultaneous edits - Offline support with sync Approaches to evaluate: - Operational Transformation (OT) - Conflict-free Replicated Data Types (CRDTs) Provide architecture and key implementation details.

Pattern 3: Real-Time Dashboard#

1// React hook for real-time metrics 2function useRealtimeMetrics(metricIds: string[]) { 3 const [metrics, setMetrics] = useState<Record<string, MetricValue>>({}); 4 5 useEffect(() => { 6 const ws = new WebSocket(`wss://api.example.com/metrics`); 7 8 ws.onopen = () => { 9 ws.send(JSON.stringify({ 10 type: 'subscribe', 11 metrics: metricIds 12 })); 13 }; 14 15 ws.onmessage = (event) => { 16 const update = JSON.parse(event.data); 17 setMetrics(prev => ({ 18 ...prev, 19 [update.metricId]: update.value 20 })); 21 }; 22 23 return () => ws.close(); 24 }, [metricIds.join(',')]); 25 26 return metrics; 27}

Connection Management#

Reconnection Logic#

Implement robust WebSocket reconnection: Requirements: - Exponential backoff - Maximum retry limit - Connection state tracking - Resubscribe on reconnect - Handle network changes (online/offline events) ```typescript class ReconnectingWebSocket { // Implementation needed }

Provide complete implementation.

### Heartbeat/Ping-Pong ```typescript class WebSocketClient { private ws: WebSocket; private heartbeatInterval: NodeJS.Timer; private heartbeatTimeout: NodeJS.Timer; connect() { this.ws = new WebSocket(this.url); this.ws.onopen = () => { this.startHeartbeat(); }; this.ws.onmessage = (event) => { if (event.data === 'pong') { this.resetHeartbeatTimeout(); return; } this.handleMessage(event); }; } private startHeartbeat() { this.heartbeatInterval = setInterval(() => { this.ws.send('ping'); this.heartbeatTimeout = setTimeout(() => { this.ws.close(); this.reconnect(); }, 5000); }, 30000); } private resetHeartbeatTimeout() { clearTimeout(this.heartbeatTimeout); } }

Scaling Real-Time Systems#

Horizontal Scaling with Redis#

Design a scalable WebSocket architecture: Requirements: - 100K concurrent connections - Multiple server instances behind load balancer - Message delivery to correct server/connection - Pub/sub for broadcast messages Architecture: - Sticky sessions or Redis for connection registry - Redis pub/sub for cross-server messaging - Room/channel abstraction Provide implementation with Socket.io and Redis adapter.

Connection Distribution#

┌─────────────────────────────────────────────┐ │ Load Balancer │ │ (sticky sessions or L4) │ └─────────────────┬───────────────────────────┘ │ ┌─────────────┼─────────────┐ ▼ ▼ ▼ ┌───────┐ ┌───────┐ ┌───────┐ │ WS 1 │ │ WS 2 │ │ WS 3 │ │ 33K │ │ 33K │ │ 33K │ └───┬───┘ └───┬───┘ └───┬───┘ │ │ │ └────────────┼────────────┘ ▼ ┌───────────┐ │ Redis │ │ Pub/Sub │ └───────────┘

Security Considerations#

Authentication#

Implement secure WebSocket authentication: Options: 1. Token in connection URL (risky - logged) 2. Token in first message (delays) 3. Cookie-based (requires same origin) 4. Token in header (not always possible) Recommend approach for: - Browser clients - Mobile clients - Server-to-server Include token refresh handling.

Rate Limiting#

1// Per-connection rate limiting 2class RateLimiter { 3 private counts = new Map<string, number>(); 4 5 isAllowed(connectionId: string, limit: number, window: number): boolean { 6 const now = Date.now(); 7 const key = `${connectionId}:${Math.floor(now / window)}`; 8 9 const count = (this.counts.get(key) || 0) + 1; 10 this.counts.set(key, count); 11 12 // Clean old windows 13 this.cleanup(now, window); 14 15 return count <= limit; 16 } 17 18 private cleanup(now: number, window: number) { 19 const threshold = Math.floor(now / window) - 1; 20 for (const key of this.counts.keys()) { 21 const [, windowKey] = key.split(':'); 22 if (parseInt(windowKey) < threshold) { 23 this.counts.delete(key); 24 } 25 } 26 } 27}

Testing Real-Time Features#

Unit Testing#

Generate tests for this WebSocket handler: ```typescript class ChatHandler { async handleMessage(socket: Socket, message: ChatMessage) { // Validate message // Store in database // Broadcast to room // Send delivery confirmation } }

Include tests for:

  • Message validation
  • Database errors
  • Broadcast failures
  • Concurrent messages
### Load Testing

Design a load test for WebSocket server:

Target:

  • 10K concurrent connections
  • 100 messages/second per connection
  • 99th percentile latency < 100ms

Tools: Artillery, k6, or custom

Provide:

  • Test scenario
  • Metrics to collect
  • Success criteria
## Frontend Patterns ### React Hook for WebSocket ```typescript function useWebSocket<T>(url: string, options: WebSocketOptions = {}) { const [status, setStatus] = useState<'connecting' | 'connected' | 'disconnected'>('connecting'); const [lastMessage, setLastMessage] = useState<T | null>(null); const wsRef = useRef<WebSocket | null>(null); const send = useCallback((data: unknown) => { if (wsRef.current?.readyState === WebSocket.OPEN) { wsRef.current.send(JSON.stringify(data)); } }, []); useEffect(() => { const ws = new WebSocket(url); wsRef.current = ws; ws.onopen = () => setStatus('connected'); ws.onclose = () => setStatus('disconnected'); ws.onmessage = (e) => setLastMessage(JSON.parse(e.data)); return () => ws.close(); }, [url]); return { status, lastMessage, send }; }

Optimistic Updates#

1function useChatRoom(roomId: string) { 2 const [messages, setMessages] = useState<Message[]>([]); 3 const { send, lastMessage } = useWebSocket(`/ws/rooms/${roomId}`); 4 5 // Handle incoming messages 6 useEffect(() => { 7 if (lastMessage) { 8 setMessages(prev => { 9 // Replace optimistic message with confirmed one 10 const filtered = prev.filter(m => m.id !== lastMessage.tempId); 11 return [...filtered, lastMessage]; 12 }); 13 } 14 }, [lastMessage]); 15 16 const sendMessage = (content: string) => { 17 const tempId = generateTempId(); 18 const optimisticMessage = { 19 id: tempId, 20 content, 21 status: 'sending', 22 timestamp: new Date() 23 }; 24 25 // Optimistic update 26 setMessages(prev => [...prev, optimisticMessage]); 27 28 // Send to server 29 send({ type: 'message', content, tempId }); 30 }; 31 32 return { messages, sendMessage }; 33}

Conclusion#

Real-time features add complexity but transform user experience. With proper patterns for connection management, scaling, and security, you can build reliable real-time systems.

AI assists at every step—choosing technologies, implementing patterns, handling edge cases, and testing under load. Start with simple SSE for one-way updates, graduate to WebSockets when you need bidirectional communication, and scale with Redis when you outgrow a single server.

Share this article

Help spread the word about Bootspring