WebSockets vs Server-Sent Events
When building real-time applications, choosing between WebSockets and Server-Sent Events (SSE) is crucial. This guide explains the differences, use cases, and when to use each technology.
When building real-time applications that require server-to-client communication, two technologies stand out: WebSockets and Server-Sent Events (SSE). Both allow servers to push data to clients in real-time, but they have different architectures, capabilities, and ideal use cases.
NOTE: This post assumes you have a basic understanding of HTTP, JavaScript, and web development concepts.
## What Are WebSockets?
WebSockets provide a full-duplex communication channel over a single TCP connection. Unlike HTTP, which is request-response based, WebSockets allow both the client and server to send messages at any time without needing to request anything.
### Key Characteristics
- Full-duplex: Both client and server can send messages simultaneously
- Persistent connection: Once established, the connection stays open
- Low latency: No overhead of HTTP headers for each message
- Binary data support: Efficient for sending binary data like images or files
### WebSocket Connection Flow
### WebSocket Example (Client)
import { useState, useEffect, useCallback, useRef } from 'react';
function useWebSocket(url: string) {
const [isConnected, setIsConnected] = useState(false);
const [lastMessage, setLastMessage] = useState<string | null>(null);
const wsRef = useRef<WebSocket | null>(null);
const connect = useCallback(() => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
return;
}
const ws = new WebSocket(url);
ws.onopen = () => {
console.log('WebSocket connected');
setIsConnected(true);
};
ws.onmessage = (event) => {
console.log('Received:', event.data);
setLastMessage(event.data);
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
ws.onclose = () => {
console.log('WebSocket disconnected');
setIsConnected(false);
// Auto-reconnect logic here
};
wsRef.current = ws;
}, [url]);
const send = useCallback((message: string | object) => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
const data = typeof message === 'string' ? message : JSON.stringify(message);
wsRef.current.send(data);
} else {
console.error('WebSocket is not connected');
}
}, []);
const disconnect = useCallback(() => {
wsRef.current?.close();
}, []);
useEffect(() => {
connect();
return () => {
disconnect();
};
}, [connect, disconnect]);
return { isConnected, lastMessage, send, disconnect, connect };
}
// Usage in a component
function WebSocketComponent() {
const { isConnected, lastMessage, send } = useWebSocket('ws://localhost:8080');
return (
<div>
<p>Status: {isConnected ? 'Connected' : 'Disconnected'}</p>
<p>Last message: {lastMessage}</p>
<button onClick={() => send({ type: 'greeting', message: 'Hello Server!' })}>
Send Message
</button>
</div>
);
}class WebSocketClient {
private ws: WebSocket | null = null;
private url: string;
constructor(url: string) {
this.url = url;
}
connect(): void {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('WebSocket connected');
};
this.ws.onmessage = (event) => {
console.log('Received:', event.data);
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
this.ws.onclose = () => {
console.log('WebSocket disconnected');
// Auto-reconnect logic here
};
}
send(message: string | object): void {
if (this.ws?.readyState === WebSocket.OPEN) {
const data = typeof message === 'string' ? message : JSON.stringify(message);
this.ws.send(data);
} else {
console.error('WebSocket is not connected');
}
}
disconnect(): void {
this.ws?.close();
}
}
// Usage
const client = new WebSocketClient('ws://localhost:8080');
client.connect();
client.send({ type: 'greeting', message: 'Hello Server!' });## What Are Server-Sent Events?
Server-Sent Events (SSE) is a server-push technology that enables a client to receive automatic updates from a server via an HTTP connection. Unlike WebSockets, SSE is unidirectional - only the server can send messages to the client.
### Key Characteristics
- Unidirectional: Only server can send messages to client
- Uses HTTP: Built on top of standard HTTP
- Automatic reconnection: Built-in reconnection handling
- Text-based: Only supports text data (JSON, plain text)
- Simple API: Easier to implement than WebSockets
### SSE Connection Flow
### SSE Example (Client)
import { useState, useEffect, useCallback, useRef } from 'react';
function useEventSource(url: string) {
const [isConnected, setIsConnected] = useState(false);
const [lastMessage, setLastMessage] = useState<any>(null);
const [notifications, setNotifications] = useState<any[]>([]);
const eventSourceRef = useRef<EventSource | null>(null);
const connect = useCallback(() => {
if (eventSourceRef.current) {
return;
}
const eventSource = new EventSource(url);
eventSource.onopen = () => {
console.log('SSE connected');
setIsConnected(true);
};
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
setLastMessage(data);
};
eventSource.onerror = (error) => {
console.error('SSE error:', error);
// Browser automatically attempts reconnection
};
// Listen for specific event types
eventSource.addEventListener('notification', (event) => {
const data = JSON.parse((event as MessageEvent).data);
console.log('Notification:', data);
setNotifications((prev) => [...prev, data]);
});
eventSourceRef.current = eventSource;
}, [url]);
const disconnect = useCallback(() => {
eventSourceRef.current?.close();
eventSourceRef.current = null;
setIsConnected(false);
}, []);
useEffect(() => {
connect();
return () => {
disconnect();
};
}, [connect, disconnect]);
return { isConnected, lastMessage, notifications, disconnect, connect };
}
// Usage in a component
function SSEComponent() {
const { isConnected, lastMessage, notifications } = useEventSource('http://localhost:8080/events');
return (
<div>
<p>Status: {isConnected ? 'Connected' : 'Disconnected'}</p>
<p>Last message: {JSON.stringify(lastMessage)}</p>
<ul>
{notifications.map((n, i) => (
<li key={i}>{JSON.stringify(n)}</li>
))}
</ul>
</div>
);
}class SSEClient {
private eventSource: EventSource | null = null;
private url: string;
constructor(url: string) {
this.url = url;
}
connect(): void {
this.eventSource = new EventSource(this.url);
this.eventSource.onopen = () => {
console.log('SSE connected');
};
this.eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};
this.eventSource.onerror = (error) => {
console.error('SSE error:', error);
// Browser automatically attempts reconnection
};
// Listen for specific event types
this.eventSource.addEventListener('notification', (event) => {
const data = JSON.parse(event.data);
console.log('Notification:', data);
});
}
disconnect(): void {
this.eventSource?.close();
}
}
// Usage
const client = new SSEClient('http://localhost:8080/events');
client.connect();## Key Differences Comparison
| Feature | WebSockets | Server-Sent Events |
|---|---|---|
| Direction | Full-duplex (bidirectional) | Unidirectional (server → client only) |
| Protocol | WebSocket protocol (ws:// or wss://) | HTTP/HTTPS |
| Reconnection | Manual implementation required | Built-in automatic reconnection |
| Data Types | Text and binary data | Text only (JSON, plain text) |
| Browser Support | Excellent (all modern browsers) | Excellent (all modern browsers) |
| Proxy/Firewall | May have issues with some proxies | Works seamlessly with HTTP |
| Server Complexity | Higher (requires stateful connection) | Lower (can use standard HTTP) |
| Scalability | More complex scaling | Easier to scale with HTTP infrastructure |
| Latency | Lower overhead | Slightly higher overhead |
| Use Case | Real-time collaboration, gaming, chat | News feeds, stock prices, notifications |
## When to Use WebSockets
### Real-Time Collaboration
WebSockets are ideal for applications where multiple users interact simultaneously:
// Real-time document editing
class DocumentCollaborator {
private ws: WebSocket;
constructor(docId: string) {
this.ws = new WebSocket(`ws://localhost:8080/collab/${docId}`);
this.ws.onmessage = (event) => {
const update = JSON.parse(event.data);
switch (update.type) {
case 'text_insert':
this.handleTextInsert(update);
break;
case 'text_delete':
this.handleTextDelete(update);
break;
case 'cursor_move':
this.handleCursorMove(update);
break;
}
};
}
sendChange(change: object): void {
this.ws.send(JSON.stringify(change));
}
// Other methods...
}### Chat Applications
Chat applications require bidirectional communication:
import { useState, useEffect, useCallback, useRef } from 'react';
interface ChatMessage {
id: string;
user: string;
text: string;
timestamp: number;
}
function useChat(roomId: string) {
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [isConnected, setIsConnected] = useState(false);
const wsRef = useRef<WebSocket | null>(null);
const connect = useCallback(() => {
const ws = new WebSocket(`ws://localhost:8080/chat/${roomId}`);
ws.onopen = () => {
setIsConnected(true);
};
ws.onmessage = (event) => {
const message: ChatMessage = JSON.parse(event.data);
setMessages((prev) => [...prev, message]);
};
ws.onclose = () => {
setIsConnected(false);
};
wsRef.current = ws;
}, [roomId]);
const sendMessage = useCallback((text: string) => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
wsRef.current.send(
JSON.stringify({
type: 'message',
text,
timestamp: Date.now()
})
);
}
}, []);
useEffect(() => {
connect();
return () => {
wsRef.current?.close();
};
}, [connect]);
return { messages, isConnected, sendMessage };
}
// Usage
function ChatRoom() {
const { messages, isConnected, sendMessage } = useChat('room123');
return (
<div>
<div>Connected: {isConnected ? 'Yes' : 'No'}</div>
<ul>
{messages.map((m) => (
<li key={m.id}>
{m.user}: {m.text}
</li>
))}
</ul>
</div>
);
}interface ChatMessage {
id: string;
user: string;
text: string;
timestamp: number;
}
class ChatClient {
private ws: WebSocket;
connect(roomId: string): void {
this.ws = new WebSocket(`ws://localhost:8080/chat/${roomId}`);
this.ws.onmessage = (event) => {
const message: ChatMessage = JSON.parse(event.data);
this.displayMessage(message);
};
}
sendMessage(text: string): void {
this.ws.send(
JSON.stringify({
type: 'message',
text,
timestamp: Date.now()
})
);
}
private displayMessage(message: ChatMessage): void {
// Display message logic
}
}### Online Gaming
Games require low-latency, bidirectional communication:
import { useState, useEffect, useCallback, useRef } from 'react';
interface GameState {
players: PlayerState[];
ball: BallState;
score: Score;
}
interface PlayerAction {
type: string;
data: any;
}
function useGameClient(gameId: string) {
const [gameState, setGameState] = useState<GameState | null>(null);
const [isConnected, setIsConnected] = useState(false);
const wsRef = useRef<WebSocket | null>(null);
const connect = useCallback(() => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
return;
}
const ws = new WebSocket(`ws://localhost:8080/game/${gameId}`);
ws.onopen = () => {
console.log('Game connected');
setIsConnected(true);
};
ws.onmessage = (event) => {
const state: GameState = JSON.parse(event.data);
setGameState(state);
};
ws.onerror = (error) => {
console.error('Game WebSocket error:', error);
};
ws.onclose = () => {
console.log('Game disconnected');
setIsConnected(false);
};
wsRef.current = ws;
}, [gameId]);
const sendAction = useCallback((action: PlayerAction) => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
wsRef.current.send(JSON.stringify(action));
} else {
console.error('Game WebSocket is not connected');
}
}, []);
const disconnect = useCallback(() => {
wsRef.current?.close();
}, []);
useEffect(() => {
connect();
return () => {
disconnect();
};
}, [connect, disconnect]);
return { gameState, isConnected, sendAction, disconnect, connect };
}
// Usage in a component
function GameBoard({ gameId }: { gameId: string }) {
const { gameState, isConnected, sendAction } = useGameClient(gameId);
const handleMove = (direction: string) => {
sendAction({ type: 'move', data: { direction } });
};
return (
<div>
<p>Status: {isConnected ? 'Connected' : 'Disconnected'}</p>
{gameState && (
<div>
<p>Score: {gameState.score}</p>
<button onClick={() => handleMove('up')}>Up</button>
<button onClick={() => handleMove('down')}>Down</button>
</div>
)}
</div>
);
}interface GameState {
players: PlayerState[];
ball: BallState;
score: Score;
}
class GameClient {
private ws: WebSocket;
connect(gameId: string): void {
this.ws = new WebSocket(`ws://localhost:8080/game/${gameId}`);
this.ws.onmessage = (event) => {
const state: GameState = JSON.parse(event.data);
this.render(state);
};
}
sendAction(action: PlayerAction): void {
this.ws.send(JSON.stringify(action));
}
}## When to Use Server-Sent Events
### Live Feeds and Updates
SSE is perfect for one-way data streams like live feeds:
import { useState, useEffect, useCallback, useRef } from 'react';
function useLiveFeed(feedType: string) {
const [feed, setFeed] = useState<any[]>([]);
const [isConnected, setIsConnected] = useState(false);
const eventSourceRef = useRef<EventSource | null>(null);
const subscribe = useCallback(() => {
if (eventSourceRef.current) {
return;
}
const eventSource = new EventSource(`/api/feeds/${feedType}`);
eventSource.onopen = () => {
console.log(`${feedType} feed connected`);
setIsConnected(true);
};
eventSource.onmessage = (event) => {
const update = JSON.parse(event.data);
setFeed((prev) => [...prev, update]);
};
eventSource.onerror = (error) => {
console.error('Feed error:', error);
};
eventSourceRef.current = eventSource;
}, [feedType]);
const unsubscribe = useCallback(() => {
eventSourceRef.current?.close();
eventSourceRef.current = null;
setIsConnected(false);
}, []);
useEffect(() => {
subscribe();
return () => {
unsubscribe();
};
}, [subscribe, unsubscribe]);
return { feed, isConnected, unsubscribe, subscribe };
}
// Usage for stock prices
function StockTicker() {
const { feed } = useLiveFeed('stock-prices');
return (
<div>
<h2>Stock Prices</h2>
<ul>
{feed.map((item, i) => (
<li key={i}>{JSON.stringify(item)}</li>
))}
</ul>
</div>
);
}
// Usage for news updates
function NewsFeed() {
const { feed } = useLiveFeed('news');
return (
<div>
<h2>News Updates</h2>
<ul>
{feed.map((item, i) => (
<li key={i}>{JSON.stringify(item)}</li>
))}
</ul>
</div>
);
}class LiveFeedClient {
private eventSource: EventSource;
subscribe(feedType: string): void {
this.eventSource = new EventSource(`/api/feeds/${feedType}`);
this.eventSource.onmessage = (event) => {
const update = JSON.parse(event.data);
this.updateFeed(update);
};
}
}
// Usage for stock prices
const stockFeed = new LiveFeedClient();
stockFeed.subscribe('stock-prices');
// Usage for news updates
const newsFeed = new LiveFeedClient();
newsFeed.subscribe('news');### Notification Systems
SSE excels at delivering notifications:
import { useState, useEffect, useCallback, useRef } from 'react';
interface Notification {
id: string;
type: 'info' | 'warning' | 'error' | 'success';
title: string;
message: string;
timestamp: number;
}
function useNotifications(userId: string) {
const [notifications, setNotifications] = useState<Notification[]>([]);
const eventSourceRef = useRef<EventSource | null>(null);
const connect = useCallback(() => {
if (eventSourceRef.current) {
return;
}
const eventSource = new EventSource(`/api/notifications/${userId}`);
eventSource.addEventListener('notification', (event) => {
const notification: Notification = JSON.parse((event as MessageEvent).data);
console.log(`[${notification.type}] ${notification.title}: ${notification.message}`);
setNotifications((prev) => [...prev, notification]);
});
eventSource.onerror = (error) => {
console.error('SSE error:', error);
};
eventSourceRef.current = eventSource;
}, [userId]);
const disconnect = useCallback(() => {
eventSourceRef.current?.close();
eventSourceRef.current = null;
}, []);
useEffect(() => {
connect();
return () => {
disconnect();
};
}, [connect, disconnect]);
return { notifications, disconnect, connect };
}
// Usage in a component
function NotificationCenter({ userId }: { userId: string }) {
const { notifications } = useNotifications(userId);
return (
<div>
<h2>Notifications</h2>
<ul>
{notifications.map((n) => (
<li key={n.id}>
[{n.type}] {n.title}: {n.message}
</li>
))}
</ul>
</div>
);
}interface Notification {
id: string;
type: 'info' | 'warning' | 'error' | 'success';
title: string;
message: string;
timestamp: number;
}
class NotificationClient {
private eventSource: EventSource;
connect(userId: string): void {
this.eventSource = new EventSource(`/api/notifications/${userId}`);
this.eventSource.addEventListener('notification', (event) => {
const notification: Notification = JSON.parse(event.data);
this.showNotification(notification);
});
}
private showNotification(notification: Notification): void {
// Display notification to user
console.log(`[${notification.type}] ${notification.title}: ${notification.message}`);
}
}### Progress Updates
SSE is great for long-running operations:
import { useState, useEffect, useCallback, useRef } from 'react';
interface ProgressUpdate {
jobId: string;
progress: number;
status: string;
completed: boolean;
}
function useJobProgress(jobId: string) {
const [progress, setProgress] = useState<ProgressUpdate | null>(null);
const [isCompleted, setIsCompleted] = useState(false);
const eventSourceRef = useRef<EventSource | null>(null);
const trackJob = useCallback(() => {
if (eventSourceRef.current) {
return;
}
const eventSource = new EventSource(`/api/jobs/${jobId}/progress`);
eventSource.onmessage = (event) => {
const update: ProgressUpdate = JSON.parse(event.data);
setProgress(update);
if (update.completed) {
setIsCompleted(true);
eventSource.close();
eventSourceRef.current = null;
}
};
eventSource.onerror = (error) => {
console.error('SSE error:', error);
};
eventSourceRef.current = eventSource;
}, [jobId]);
const stopTracking = useCallback(() => {
eventSourceRef.current?.close();
eventSourceRef.current = null;
}, []);
useEffect(() => {
trackJob();
return () => {
stopTracking();
};
}, [trackJob, stopTracking]);
return { progress, isCompleted, stopTracking };
}
// Usage in a component
function JobTracker({ jobId }: { jobId: string }) {
const { progress, isCompleted } = useJobProgress(jobId);
return (
<div>
<h3>Job Progress</h3>
{progress ? (
<div>
<p>Status: {progress.status}</p>
<p>Progress: {progress.progress}%</p>
{isCompleted && <p>Job completed!</p>}
</div>
) : (
<p>Connecting...</p>
)}
</div>
);
}interface ProgressUpdate {
jobId: string;
progress: number;
status: string;
completed: boolean;
}
class ProgressTracker {
trackJob(jobId: string): void {
const eventSource = new EventSource(`/api/jobs/${jobId}/progress`);
eventSource.onmessage = (event) => {
const update: ProgressUpdate = JSON.parse(event.data);
this.updateProgressBar(update.progress);
if (update.completed) {
eventSource.close();
this.showCompletionMessage();
}
};
}
private updateProgressBar(progress: number): void {
console.log(`Progress: ${progress}%`);
}
private showCompletionMessage(): void {
console.log('Job completed!');
}
}## Real-Time Data Streaming
SSE is excellent for streaming real-time data:
import { useState, useEffect, useCallback, useRef } from 'react';
function useDataStream(metrics: string[]) {
const [streamData, setStreamData] = useState<any[]>([]);
const [isConnected, setIsConnected] = useState(false);
const eventSourceRef = useRef<EventSource | null>(null);
const startStream = useCallback(() => {
if (eventSourceRef.current) {
return;
}
const params = new URLSearchParams({ metrics: metrics.join(',') });
const eventSource = new EventSource(`/api/stream?${params}`);
eventSource.onopen = () => {
console.log('Data stream connected');
setIsConnected(true);
};
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
setStreamData((prev) => [...prev, data]);
};
eventSource.onerror = (error) => {
console.error('Stream error:', error);
};
eventSourceRef.current = eventSource;
}, [metrics]);
const stopStream = useCallback(() => {
eventSourceRef.current?.close();
eventSourceRef.current = null;
setIsConnected(false);
}, []);
useEffect(() => {
startStream();
return () => {
stopStream();
};
}, [startStream, stopStream]);
return { streamData, isConnected, startStream, stopStream };
}
// Usage in a component
function DataDashboard({ metrics }: { metrics: string[] }) {
const { streamData, isConnected } = useDataStream(metrics);
return (
<div>
<h2>Real-Time Dashboard</h2>
<p>Status: {isConnected ? 'Connected' : 'Disconnected'}</p>
<ul>
{streamData.map((data, i) => (
<li key={i}>{JSON.stringify(data)}</li>
))}
</ul>
</div>
);
}class DataStreamClient {
private eventSource: EventSource | null = null;
startStream(metrics: string[]): void {
const params = new URLSearchParams({ metrics: metrics.join(',') });
this.eventSource = new EventSource(`/api/stream?${params}`);
this.eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
this.updateDashboard(data);
};
}
stopStream(): void {
this.eventSource?.close();
}
private updateDashboard(data: any): void {
// Update UI with streamed data
}
}## Combining Both Technologies
Sometimes the best approach is to use both technologies together based on the use case:
import { useState, useEffect, useCallback, useRef } from 'react';
function useHybridClient(roomId: string, userId: string) {
const [collabData, setCollabData] = useState<any>(null);
const [notifications, setNotifications] = useState<any[]>([]);
const wsRef = useRef<WebSocket | null>(null);
const eventSourceRef = useRef<EventSource | null>(null);
// Use WebSocket for real-time collaboration
const connectCollaboration = useCallback(() => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
return;
}
const ws = new WebSocket(`ws://localhost:8080/collab/${roomId}`);
ws.onopen = () => {
console.log('Collaboration WebSocket connected');
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
setCollabData(data);
};
ws.onerror = (error) => {
console.error('Collaboration WebSocket error:', error);
};
wsRef.current = ws;
}, [roomId]);
// Use SSE for notifications
const subscribeNotifications = useCallback(() => {
if (eventSourceRef.current) {
return;
}
const eventSource = new EventSource(`/api/notifications/${userId}`);
eventSource.addEventListener('notification', (event) => {
const notification = JSON.parse((event as MessageEvent).data);
setNotifications((prev) => [...prev, notification]);
});
eventSourceRef.current = eventSource;
}, [userId]);
const disconnectAll = useCallback(() => {
wsRef.current?.close();
eventSourceRef.current?.close();
eventSourceRef.current = null;
}, []);
useEffect(() => {
connectCollaboration();
subscribeNotifications();
return () => {
disconnectAll();
};
}, [connectCollaboration, subscribeNotifications, disconnectAll]);
return { collabData, notifications, disconnectAll };
}
// Usage in a component
function HybridApp({ roomId, userId }: { roomId: string; userId: string }) {
const { collabData, notifications } = useHybridClient(roomId, userId);
return (
<div>
<h2>Collaborative Room</h2>
{collabData && <div>Collab Data: {JSON.stringify(collabData)}</div>}
<h3>Notifications</h3>
<ul>
{notifications.map((n, i) => (
<li key={i}>{JSON.stringify(n)}</li>
))}
</ul>
</div>
);
}class HybridClient {
private ws: WebSocket | null = null;
private eventSource: EventSource | null = null;
// Use WebSocket for real-time collaboration
connectCollaboration(roomId: string): void {
this.ws = new WebSocket(`ws://localhost:8080/collab/${roomId}`);
this.ws.onmessage = (event) => this.handleCollaboration(event);
}
// Use SSE for notifications
subscribeNotifications(userId: string): void {
this.eventSource = new EventSource(`/api/notifications/${userId}`);
this.eventSource.addEventListener('notification', (event) => {
this.handleNotification(event);
});
}
private handleCollaboration(event: MessageEvent): void {
// Handle collaboration updates
}
private handleNotification(event: MessageEvent): void {
// Handle notifications
}
}## Server-Side Examples
### Node.js WebSocket Server
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws) => {
console.log('Client connected');
ws.on('message', (data) => {
// Broadcast to all clients
wss.clients.forEach((client) => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(data);
}
});
});
ws.on('close', () => {
console.log('Client disconnected');
});
});### Node.js SSE Server
import express from 'express';
const app = express();
app.get('/events', (req, res) => {
// Set headers for SSE
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// Send initial message
res.write('event: connected\ndata: {"message": "Connected!"}\n\n');
// Send updates every second
const interval = setInterval(() => {
const data = JSON.stringify({
timestamp: Date.now(),
value: Math.random()
});
res.write(`data: ${data}\n\n`);
}, 1000);
// Clean up on client disconnect
req.on('close', () => {
clearInterval(interval);
});
});
app.listen(8080);## Decision Framework
Use this decision tree to choose between WebSockets and SSE:
Do you need client → server communication?
├── Yes → Use WebSockets
└── No → Continue
Do you need binary data support?
├── Yes → Use WebSockets
└── No → Continue
Is simplicity more important than lowest latency?
├── Yes → Use SSE
└── No → Consider WebSockets## Conclusion
Both WebSockets and Server-Sent Events are powerful technologies for real-time communication:
- Choose WebSockets for interactive, bidirectional applications like chat, collaboration, and gaming
- Choose SSE for one-way data streaming like notifications, live feeds, and updates
Consider your specific requirements: communication direction, data types, complexity, and scalability needs. Often, the right choice becomes clear when you evaluate what you're trying to achieve rather than choosing based on technical features alone.
Published on January 12, 2026
17 min read
Found an Issue!
Find an issue with this post? Think you could clarify, update or add something? All my posts are available to edit on Github. Any fix, little or small, is appreciated!
Edit on GitHubLast updated on
