WebSockets in the Browser
WebSockets provide a persistent, full-duplex connection between your browser and a server. Unlike traditional HTTP requests where the client always initiates communication, WebSockets allow both sides to send messages at any time—making them ideal for real-time applications like chat, live dashboards, and collaborative tools.
Why WebSockets?
HTTP is inherently request-response based. To get updates from a server, your client must repeatedly poll for changes:
// Polling approach - inefficient
async function checkForUpdates() {
const response = await fetch('/api/status');
const data = await response.json();
updateUI(data);
}
setInterval(checkForUpdates, 2000); // Check every 2 seconds
This wastes bandwidth and introduces latency. WebSockets solve this:
// WebSocket - server pushes updates instantly
const socket = new WebSocket('wss://example.com/ws');
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
updateUI(data); // Immediate update
};
WebSocket vs HTTP Comparison
| Aspect | HTTP Polling | WebSockets |
|---|---|---|
| Latency | 0-2 seconds delay | Instant |
| Overhead | New connection each request | Single persistent connection |
| Server load | High (many requests) | Low (one connection) |
| Bi-directional | No (client initiates) | Yes (both can send) |
| Browser support | All browsers | All modern browsers |
Creating a WebSocket Connection
The WebSocket API is straightforward:
// Connect to a WebSocket server
const socket = new WebSocket('wss://example.com/ws');
// Connection opened
socket.addEventListener('open', (event) => {
console.log('Connected to WebSocket server');
socket.send(JSON.stringify({ type: 'hello' }));
});
// Receive messages
socket.addEventListener('message', (event) => {
console.log('Message from server:', event.data);
});
// Handle errors
socket.addEventListener('error', (event) => {
console.error('WebSocket error:', event);
});
// Connection closed
socket.addEventListener('close', (event) => {
console.log('Disconnected:', event.code, event.reason);
});
Notice the URL scheme: ws:// for unencrypted or wss:// for encrypted connections. Always use wss:// in production—it encrypts your data and works with modern browsers without warnings.
Sending and Receiving Data
WebSockets send raw text or binary data. JSON is the common choice:
// Send a JSON message
function sendMessage(type, payload) {
socket.send(JSON.stringify({ type, payload, timestamp: Date.now() }));
}
// Different message types
sendMessage('chat', { text: 'Hello, world!' });
sendMessage('cursor', { x: 100, y: 200 });
sendMessage('ping', {});
On the receiving end, parse the data:
socket.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
switch (message.type) {
case 'chat':
displayChatMessage(message.payload);
break;
case 'update':
handleStateUpdate(message.payload);
break;
case 'pong':
console.log('Server acknowledged our ping');
break;
}
} catch (e) {
// Handle non-JSON messages
console.log('Raw message:', event.data);
}
};
Handling Connection State
The WebSocket has a readyState property that tells you its current state:
console.log(socket.readyState);
// Values:
WebSocket.CONNECTING // 0 - Connection not yet established
WebSocket.OPEN // 1 - Connection established, ready to communicate
WebSocket.CLOSING // 2 - Connection closing
WebSocket.CLOSED // 3 - Connection closed
This helps you avoid sending messages when the connection isn’t ready:
function safeSend(data) {
if (socket.readyState === WebSocket.OPEN) {
socket.send(data);
} else {
// Queue message or retry later
pendingMessages.push(data);
}
}
Automatic Reconnection
Network interruptions happen. Build reconnection logic into your code:
class WebSocketClient {
constructor(url) {
this.url = url;
this.connect();
}
connect() {
this.socket = new WebSocket(this.url);
this.socket.onclose = (event) => {
console.log('Connection lost, reconnecting...');
// Exponential backoff: 1s, 2s, 4s, 8s...
setTimeout(() => this.connect(), this.retryDelay || 1000);
this.retryDelay = Math.min((this.retryDelay || 1000) * 2, 30000);
};
this.socket.onopen = () => {
console.log('Reconnected');
this.retryDelay = 1000; // Reset backoff
this.flushPending();
};
}
send(data) {
if (this.socket.readyState === WebSocket.OPEN) {
this.socket.send(data);
} else {
this.pending = this.pending || [];
this.pending.push(data);
}
}
flushPending() {
if (this.pending) {
this.pending.forEach(msg => this.socket.send(msg));
this.pending = [];
}
}
}
const client = new WebSocketClient('wss://example.com/ws');
This client automatically reconnects with exponential backoff, preventing server overload during outages.
Real-World Example: Chat Application
Here’s a practical chat implementation:
class ChatClient {
constructor(serverUrl) {
this.serverUrl = serverUrl;
this.connect();
}
connect() {
this.socket = new WebSocket(this.serverUrl);
this.socket.onmessage = (event) => {
const message = JSON.parse(event.data);
this.handleMessage(message);
};
this.socket.onopen = () => {
this.sendJoin();
};
}
sendJoin() {
this.send({ type: 'join', username: this.username });
}
sendMessage(text) {
this.send({
type: 'message',
text: text,
timestamp: Date.now()
});
}
send(type, payload) {
this.socket.send(JSON.stringify({ type, ...payload }));
}
handleMessage(message) {
switch (message.type) {
case 'message':
this.displayMessage(message);
break;
case 'users':
this.updateUserList(message.users);
break;
case 'error':
this.showError(message.text);
break;
}
}
displayMessage(msg) {
const chat = document.getElementById('chat');
const div = document.createElement('div');
div.textContent = `${msg.username}: ${msg.text}`;
chat.appendChild(div);
}
}
// Usage
const chat = new ChatClient('wss://chat.example.com');
// Send a message
chat.sendMessage('Hello, everyone!');
Secure WebSocket Considerations
When deploying WebSockets in production:
- Always use wss:// — encrypts the connection
- Validate origin — check the Origin header on the server to prevent cross-site attacks
- Authenticate during handshake — pass tokens as query parameters or headers during connection:
const socket = new WebSocket('wss://api.example.com/ws?token=YOUR_JWT'); - Handle sensitive data carefully — WebSocket connections don’t automatically include cookies; use explicit authentication
Browser Compatibility
WebSockets are supported in all modern browsers:
- Chrome 4+
- Firefox 11+
- Safari 4+
- Edge 12+
- IE 10+ (with limitations)
For older browsers, libraries like socket.io provide fallbacks, though they add overhead.
Summary
WebSockets enable real-time, bidirectional communication between browser and server. Key points:
- Create connections with
new WebSocket(url)usingwss://for production - Send messages with
.send()— use JSON for structured data - Handle state — check
readyStatebefore sending - Implement reconnection — automatic retry with backoff handles network issues
- Always authenticate — validate users during the WebSocket handshake
In the next tutorial, you’ll learn about IndexedDB for storing data client-side—another powerful browser API for building offline-capable applications.