Real-time communication is essential for modern web applications. From live chat to collaborative editing, real-time features enhance user engagement and create immersive experiences. This guide covers WebSockets, Server-Sent Events, and WebRTC for building real-time applications.
WebSockets: Full-Duplex Communication
WebSockets provide full-duplex, bidirectional communication between client and server. Once established, the connection remains open, allowing instant message exchange.
Server-Side WebSocket Implementation (Node.js)
// Using ws library
import WebSocket, { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
const clients = new Set();
wss.on('connection', (ws, req) => {
clients.add(ws);
const clientId = req.url.split('?id=')[1];
ws.on('message', (message) => {
const data = JSON.parse(message.toString());
// Broadcast to all clients except sender
clients.forEach((client) => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({
type: 'message',
from: clientId,
data,
timestamp: Date.now(),
}));
}
});
});
ws.on('close', () => {
clients.delete(ws);
});
// Send welcome message
ws.send(JSON.stringify({
type: 'connected',
message: 'Welcome to the chat!',
}));
});
Client-Side WebSocket Implementation
// React hook for WebSocket
function useWebSocket(url: string) {
const [socket, setSocket] = useState(null);
const [messages, setMessages] = useState([]);
const [isConnected, setIsConnected] = useState(false);
useEffect(() => {
const ws = new WebSocket(url);
ws.onopen = () => {
setIsConnected(true);
console.log('WebSocket connected');
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
setMessages(prev => [...prev, data]);
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
ws.onclose = () => {
setIsConnected(false);
// Reconnect logic
setTimeout(() => {
setSocket(new WebSocket(url));
}, 3000);
};
setSocket(ws);
return () => {
ws.close();
};
}, [url]);
const sendMessage = (message: any) => {
if (socket?.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify(message));
}
};
return { messages, sendMessage, isConnected };
}
Server-Sent Events (SSE): Server-to-Client Streaming
SSE is simpler than WebSockets for one-way server-to-client communication. It's built on HTTP, making it easier to implement and debug.
Server-Side SSE Implementation
// Next.js API route
export async function GET(request: Request) {
const encoder = new TextEncoder();
const stream = new ReadableStream({
start(controller) {
// Send initial connection message
const data = `data: {"type":"connected"}\n\n`;
controller.enqueue(encoder.encode(data));
// Send updates every second
const interval = setInterval(() => {
const update = {
type: 'update',
data: { timestamp: Date.now(), value: Math.random() },
};
const message = `data: ${JSON.stringify(update)}\n\n`;
controller.enqueue(encoder.encode(message));
}, 1000);
// Cleanup on close
request.signal.addEventListener('abort', () => {
clearInterval(interval);
controller.close();
});
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
}
Client-Side SSE Implementation
function useServerSentEvents(url: string) {
const [data, setData] = useState(null);
useEffect(() => {
const eventSource = new EventSource(url);
eventSource.onmessage = (event) => {
const parsed = JSON.parse(event.data);
setData(parsed);
};
eventSource.onerror = () => {
eventSource.close();
// Reconnect after delay
setTimeout(() => {
setData(null);
}, 5000);
};
return () => {
eventSource.close();
};
}, [url]);
return data;
}
WebRTC: Peer-to-Peer Communication
WebRTC enables peer-to-peer audio, video, and data communication directly between browsers without intermediaries. Perfect for video conferencing, file sharing, or gaming.
WebRTC Connection Setup
// Signaling server (for ICE candidates and SDP exchange)
const pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
});
// Handle ICE candidates
pc.onicecandidate = (event) => {
if (event.candidate) {
// Send to other peer via signaling server
signaling.send({
type: 'ice-candidate',
candidate: event.candidate,
});
}
};
// Handle remote stream
pc.ontrack = (event) => {
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = event.streams[0];
};
// Create offer
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
signaling.send({ type: 'offer', offer });
// Handle answer
signaling.on('answer', async ({ answer }) => {
await pc.setRemoteDescription(answer);
});
// Add local stream
const localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
localStream.getTracks().forEach(track => {
pc.addTrack(track, localStream);
});
Use Cases and When to Use Each
WebSockets - Best For:
- Bidirectional real-time chat
- Collaborative editing (Google Docs style)
- Live updates and notifications
- Gaming with real-time state sync
- Any scenario requiring instant two-way communication
SSE - Best For:
- Live feeds and updates (social media, news)
- Progress bars and status updates
- Real-time dashboards and metrics
- Stock prices or live scores
- When you only need server-to-client communication
WebRTC - Best For:
- Video and audio conferencing
- Peer-to-peer file sharing
- Screen sharing
- Low-latency gaming
- Any direct peer-to-peer communication
Scaling Real-Time Applications
Horizontal Scaling with Redis Pub/Sub
// Multiple server instances sharing state via Redis
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
const pub = new Redis(process.env.REDIS_URL);
wss.on('connection', (ws) => {
// Subscribe to channel
redis.subscribe('messages');
ws.on('message', (message) => {
// Publish to all servers
pub.publish('messages', message.toString());
});
});
redis.on('message', (channel, message) => {
// Broadcast to all clients on this server
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
Error Handling and Reconnection
Implement robust error handling and automatic reconnection:
- Exponential backoff for reconnection attempts
- Connection state indicators in UI
- Message queuing during disconnection
- Heartbeat/ping mechanisms to detect dead connections
- Graceful degradation when real-time fails
Performance Optimization
- Compress messages (use binary format or compression)
- Batch multiple updates into single messages
- Throttle rapid updates on client side
- Use message acknowledgments for reliability
- Implement connection pooling and resource limits
Real-World Example
We built a real-time collaborative document editor:
- WebSockets for bidirectional communication
- Operational Transform (OT) for conflict resolution
- Redis Pub/Sub for multi-server scaling
- Message batching to reduce network overhead
- Result: Sub-100ms latency, support for 1000+ concurrent users per document
Security Considerations
- Authenticate connections before accepting
- Validate all messages on server side
- Rate limit connections and messages
- Use WSS (WebSocket Secure) for encrypted connections
- Implement proper CORS policies
- Sanitize user input to prevent injection attacks
Conclusion
Real-time communication is essential for modern web applications. Choose WebSockets for bidirectional communication, SSE for server-to-client streaming, and WebRTC for peer-to-peer connections. Plan for scaling from the start, implement robust error handling, and prioritize security. Start simple and add complexity as needed.