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 React, { , , , } from 'react';
function (: string) {
const [, ] = (false);
const [, ] = <string | null>(null);
const = <WebSocket | null>(null);
const = (() => {
if (.?. === .) {
return;
}
const = new ();
. = () => {
.('WebSocket connected');
(true);
};
. = () => {
.('Received:', .);
(.);
};
. = () => {
.('WebSocket error:', );
};
. = () => {
.('WebSocket disconnected');
(false);
// Auto-reconnect logic here
};
. = ;
}, []);
const = ((: string | object) => {
if (.?. === .) {
const = typeof === 'string' ? : .();
..();
} else {
.('WebSocket is not connected');
}
}, []);
const = (() => {
.?.();
}, []);
(() => {
();
return () => {
();
};
}, [, ]);
return { , , , , };
}
// Usage in a component
function () {
const { , , } = ('ws://localhost:8080');
return (
<>
<>Status: { ? 'Connected' : 'Disconnected'}</>
<>Last message: {}</>
< ={() => ({ : 'greeting', : 'Hello Server!' })}>
Send Message
</>
</>
);
}class {
private : WebSocket | null = null;
private : string;
constructor(: string) {
this. = ;
}
(): void {
this. = new (this.);
this.. = () => {
.('WebSocket connected');
};
this.. = () => {
.('Received:', .);
};
this.. = () => {
.('WebSocket error:', );
};
this.. = () => {
.('WebSocket disconnected');
// Auto-reconnect logic here
};
}
(: string | object): void {
if (this.?. === .) {
const = typeof === 'string' ? : .();
this..();
} else {
.('WebSocket is not connected');
}
}
(): void {
this.?.();
}
}
// Usage
const = new ('ws://localhost:8080');
.();
.({ : 'greeting', : '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 React, { , , , } from 'react';
function (: string) {
const [, ] = (false);
const [, ] = <any>(null);
const [, ] = <any[]>([]);
const = <EventSource | null>(null);
const = (() => {
if (.) {
return;
}
const = new ();
. = () => {
.('SSE connected');
(true);
};
. = () => {
const = .(.);
.('Received:', );
();
};
. = () => {
.('SSE error:', );
// Browser automatically attempts reconnection
};
// Listen for specific event types
.('notification', () => {
const = .(( as ).);
.('Notification:', );
(() => [..., ]);
});
. = ;
}, []);
const = (() => {
.?.();
. = null;
(false);
}, []);
(() => {
();
return () => {
();
};
}, [, ]);
return { , , , , };
}
// Usage in a component
function () {
const { , , } = ('http://localhost:8080/events');
return (
<>
<>Status: { ? 'Connected' : 'Disconnected'}</>
<>Last message: {.()}</>
<>
{.((, ) => (
< ={}>{.()}</>
))}
</>
</>
);
}class {
private : EventSource | null = null;
private : string;
constructor(: string) {
this. = ;
}
(): void {
this. = new (this.);
this.. = () => {
.('SSE connected');
};
this.. = () => {
const = .(.);
.('Received:', );
};
this.. = () => {
.('SSE error:', );
// Browser automatically attempts reconnection
};
// Listen for specific event types
this..('notification', () => {
const = .(.);
.('Notification:', );
});
}
(): void {
this.?.();
}
}
// Usage
const = new ('http://localhost:8080/events');
.();## 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 React, { , , , } from 'react';
interface ChatMessage {
: string;
: string;
: string;
: number;
}
function (: string) {
const [, ] = <ChatMessage[]>([]);
const [, ] = (false);
const = <WebSocket | null>(null);
const = (() => {
const = new (`ws://localhost:8080/chat/${}`);
. = () => {
(true);
};
. = () => {
const : ChatMessage = .(.);
(() => [..., ]);
};
. = () => {
(false);
};
. = ;
}, []);
const = ((: string) => {
if (.?. === .) {
..(
.({
: 'message',
,
: .()
})
);
}
}, []);
(() => {
();
return () => {
.?.();
};
}, []);
return { , , };
}
// Usage
function () {
const { , , } = ('room123');
return (
<>
<>Connected: { ? 'Yes' : 'No'}</>
<>
{.(() => (
< ={.}>
{.}: {.}
</>
))}
</>
</>
);
}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 React, { 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 React, { , , , } from 'react';
function (: string) {
const [, ] = <any[]>([]);
const [, ] = (false);
const = <EventSource | null>(null);
const = (() => {
if (.) {
return;
}
const = new (`/api/feeds/${}`);
. = () => {
.(`${} feed connected`);
(true);
};
. = () => {
const = .(.);
(() => [..., ]);
};
. = () => {
.('Feed error:', );
};
. = ;
}, []);
const = (() => {
.?.();
. = null;
(false);
}, []);
(() => {
();
return () => {
();
};
}, [, ]);
return { , , , };
}
// Usage for stock prices
function () {
const { } = ('stock-prices');
return (
<>
<>Stock Prices</>
<>
{.((, ) => (
< ={}>{.()}</>
))}
</>
</>
);
}
// Usage for news updates
function () {
const { } = ('news');
return (
<>
<>News Updates</>
<>
{.((, ) => (
< ={}>{.()}</>
))}
</>
</>
);
}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 React, { , , , } from 'react';
interface Notification {
: string;
: 'info' | 'warning' | 'error' | 'success';
: string;
: string;
: number;
}
function (: string) {
const [, ] = <Notification[]>([]);
const = <EventSource | null>(null);
const = (() => {
if (.) {
return;
}
const = new (`/api/notifications/${}`);
.('notification', () => {
const : Notification = .(( as ).);
.(`[${.}] ${.}: ${.}`);
(() => [..., ]);
});
. = () => {
.('SSE error:', );
};
. = ;
}, []);
const = (() => {
.?.();
. = null;
}, []);
(() => {
();
return () => {
();
};
}, [, ]);
return { , , };
}
// Usage in a component
function ({ }: { : string }) {
const { } = ();
return (
<>
<>Notifications</>
<>
{.(() => (
< ={.}>
[{.}] {.}: {.}
</>
))}
</>
</>
);
}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 React, { , , , } from 'react';
interface ProgressUpdate {
: string;
: number;
: string;
: boolean;
}
function (: string) {
const [, ] = <ProgressUpdate | null>(null);
const [, ] = (false);
const = <EventSource | null>(null);
const = (() => {
if (.) {
return;
}
const = new (`/api/jobs/${}/progress`);
. = () => {
const : ProgressUpdate = .(.);
();
if (.) {
(true);
.();
. = null;
}
};
. = () => {
.('SSE error:', );
};
. = ;
}, []);
const = (() => {
.?.();
. = null;
}, []);
(() => {
();
return () => {
();
};
}, [, ]);
return { , , };
}
// Usage in a component
function ({ }: { : string }) {
const { , } = ();
return (
<>
<>Job Progress</>
{ ? (
<>
<>Status: {.}</>
<>Progress: {.}%</>
{ && <>Job completed!</>}
</>
) : (
<>Connecting...</>
)}
</>
);
}interface ProgressUpdate {
: string;
: number;
: string;
: boolean;
}
class {
(: string): void {
const = new (`/api/jobs/${}/progress`);
. = () => {
const : ProgressUpdate = .(.);
this.(.);
if (.) {
.();
this.();
}
};
}
private (: number): void {
.(`Progress: ${}%`);
}
private (): void {
.('Job completed!');
}
}## Real-Time Data Streaming
SSE is excellent for streaming real-time data:
import React, { , , , } from 'react';
function (: string[]) {
const [, ] = <any[]>([]);
const [, ] = (false);
const = <EventSource | null>(null);
const = (() => {
if (.) {
return;
}
const = new ({ : .(',') });
const = new (`/api/stream?${}`);
. = () => {
.('Data stream connected');
(true);
};
. = () => {
const = .(.);
(() => [..., ]);
};
. = () => {
.('Stream error:', );
};
. = ;
}, []);
const = (() => {
.?.();
. = null;
(false);
}, []);
(() => {
();
return () => {
();
};
}, [, ]);
return { , , , };
}
// Usage in a component
function ({ }: { : string[] }) {
const { , } = ();
return (
<>
<>Real-Time Dashboard</>
<>Status: { ? 'Connected' : 'Disconnected'}</>
<>
{.((, ) => (
< ={}>{.()}</>
))}
</>
</>
);
}class {
private : EventSource | null = null;
(: string[]): void {
const = new ({ : .(',') });
this. = new (`/api/stream?${}`);
this.. = () => {
const = .(.);
this.();
};
}
(): void {
this.?.();
}
private (: 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 React, { , , , } from 'react';
function (: string, : string) {
const [, ] = <any>(null);
const [, ] = <any[]>([]);
const = <WebSocket | null>(null);
const = <EventSource | null>(null);
// Use WebSocket for real-time collaboration
const = (() => {
if (.?. === .) {
return;
}
const = new (`ws://localhost:8080/collab/${}`);
. = () => {
.('Collaboration WebSocket connected');
};
. = () => {
const = .(.);
();
};
. = () => {
.('Collaboration WebSocket error:', );
};
. = ;
}, []);
// Use SSE for notifications
const = (() => {
if (.) {
return;
}
const = new (`/api/notifications/${}`);
.('notification', () => {
const = .(( as ).);
(() => [..., ]);
});
. = ;
}, []);
const = (() => {
.?.();
.?.();
. = null;
}, []);
(() => {
();
();
return () => {
();
};
}, [, , ]);
return { , , };
}
// Usage in a component
function ({ , }: { : string; : string }) {
const { , } = (, );
return (
<>
<>Collaborative Room</>
{ && <>Collab Data: {.()}</>}
<>Notifications</>
<>
{.((, ) => (
< ={}>{.()}</>
))}
</>
</>
);
}class {
private : WebSocket | null = null;
private : EventSource | null = null;
// Use WebSocket for real-time collaboration
(: string): void {
this. = new (`ws://localhost:8080/collab/${}`);
this.. = () => this.();
}
// Use SSE for notifications
(: string): void {
this. = new (`/api/notifications/${}`);
this..('notification', () => {
this.();
});
}
private (: ): void {
// Handle collaboration updates
}
private (: ): 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
18 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
