Websocket Error Handling
title: "WebSocket Error Handling" description: "Learn how to handle WebSocket errors gracefully including connection failures, protocol errors, frame parsing errors, and server-side exceptions." weight: 24 date: 2026-06-28 lastmod: 2026-06-28 tags: ["apis", "websocket"]
Error handling in WebSocket applications covers connection failures, protocol violations, message parsing errors, and server-side exceptions. Proper error handling prevents crashes and provides meaningful feedback.
## What You'll Learn
- Common WebSocket error types
- Server-side error handling
- Client-side error recovery
- Error propagation patterns
- Logging and monitoring errors
## Why It Matters
WebSocket connections are long-lived and subject to many failure modes. Without proper error handling, failures cascade, connections leak, and users experience silent data loss.
## Real-World Use
A real-time stock trading platform categorizes WebSocket errors into network (reconnect), protocol (close connection), business (notify user), and system (alert operations). Each category triggers an appropriate response.
## Flow Chart
```mermaid
flowchart TD
A[WebSocket Error] --> B{Error Type}
B --> C[Connection Error]
B --> D[Protocol Error]
B --> E[Application Error]
B --> F[Timeout Error]
C --> G[Reconnect]
D --> H[Close Connection]
E --> I[Notify User]
F --> J[Retry or Reconnect]
Code Examples
Example 1: Comprehensive Server Error Handling
const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 8080 });
server.on('connection', (ws, req) => {
console.log('New connection from:', req.socket.remoteAddress);
// Handle errors at the connection level
ws.on('error', (error) => {
console.error('WebSocket error:', error.message);
// Log error details
logError({
type: 'websocket_error',
message: error.message,
code: error.code,
remoteAddress: req.socket.remoteAddress,
timestamp: new Date().toISOString(),
});
});
// Handle messages with validation
ws.on('message', (data) => {
try {
const parsed = JSON.parse(data);
if (!parsed.type || typeof parsed.type !== 'string') {
sendError(ws, 400, 'Invalid message format');
return;
}
handleMessage(ws, parsed);
} catch (parseError) {
sendError(ws, 400, 'Invalid JSON payload');
logError({
type: 'parse_error',
raw: data.toString().substring(0, 200),
error: parseError.message,
});
}
});
// Handle unexpected disconnection
ws.on('close', (code, reason) => {
const closeReason = reason?.toString() || 'No reason provided';
console.log(`Connection closed: ${code} - ${closeReason}`);
cleanupUserResources(ws);
});
});
function sendError(ws, code, message) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
type: 'error',
code,
message,
timestamp: Date.now(),
}));
}
}
function handleMessage(ws, message) {
try {
switch (message.type) {
case 'subscribe':
if (!message.channel) {
sendError(ws, 400, 'channel is required for subscribe');
return;
}
subscribeToChannel(ws, message.channel);
break;
case 'publish':
if (!message.topic || !message.payload) {
sendError(ws, 400, 'topic and payload are required');
return;
}
publishMessage(ws, message);
break;
default:
sendError(ws, 400, `Unknown message type: ${message.type}`);
}
} catch (handlerError) {
console.error('Handler error:', handlerError);
sendError(ws, 500, 'Internal server error');
logError({
type: 'handler_error',
message: message,
error: handlerError.message,
stack: handlerError.stack,
});
}
}
server.on('error', (error) => {
console.error('Server error:', error);
// Server-level error handling (e.g., port conflict)
});
Expected output: Server handles parsing errors, validation errors, business logic errors, and unexpected exceptions with appropriate responses and logging.
Example 2: Client Error Recovery
class ResilientWebSocketClient {
constructor(url) {
this.url = url;
this.errorCount = 0;
this.maxErrors = 5;
this.connect();
}
connect() {
try {
this.ws = new WebSocket(this.url);
} catch (error) {
console.error('Failed to create WebSocket:', error);
this.scheduleReconnect();
return;
}
this.ws.onopen = () => {
this.errorCount = 0;
console.log('Connected');
};
this.ws.onerror = (event) => {
this.errorCount++;
console.error('WebSocket error:', event.message || 'Unknown error');
// Categorize errors
const errorInfo = this.categorizeError(event);
switch (errorInfo.category) {
case 'network':
// Network errors are recoverable
console.log('Network error, will reconnect');
break;
case 'protocol':
// Protocol errors indicate server incompatibility
console.error('Protocol error, closing permanently');
this.ws.close(1002, 'Protocol error');
break;
case 'timeout':
// Timeout errors may recover
console.log('Timeout, retrying...');
break;
default:
console.warn('Unknown error category');
}
};
this.ws.onclose = (event) => {
const closeInfo = {
code: event.code,
reason: event.reason,
wasClean: event.wasClean,
};
console.log('Connection closed:', closeInfo);
if (!event.wasClean && this.errorCount < this.maxErrors) {
this.scheduleReconnect();
}
};
}
categorizeError(event) {
if (event.message?.includes('timeout') || event.message?.includes('timed out')) {
return { category: 'timeout' };
}
if (event.message?.includes('handshake') || event.message?.includes('upgrade')) {
return { category: 'protocol' };
}
if (event.message?.includes('ENETUNREACH') || event.message?.includes('ECONNREFUSED')) {
return { category: 'network' };
}
return { category: 'unknown' };
}
scheduleReconnect() {
const delay = Math.min(1000 * Math.pow(2, this.errorCount), 30000);
console.log(`Reconnecting in ${delay}ms`);
setTimeout(() => this.connect(), delay);
}
}
Expected output: Client categorizes errors and responds appropriately: reconnecting for network errors, closing for protocol errors, and tracking error counts.
Example 3: Error Handling with Close Codes
const WebSocket = require('ws');
// Standard WebSocket close codes
const CLOSE_CODES = {
NORMAL_CLOSURE: 1000,
GOING_AWAY: 1001,
PROTOCOL_ERROR: 1002,
UNSUPPORTED_DATA: 1003,
NO_STATUS_RECEIVED: 1005,
ABNORMAL_CLOSURE: 1006,
INVALID_PAYLOAD: 1007,
POLICY_VIOLATION: 1008,
MESSAGE_TOO_BIG: 1009,
MANDATORY_EXTENSION: 1010,
INTERNAL_ERROR: 1011,
SERVICE_RESTART: 1012,
TRY_AGAIN_LATER: 1013,
};
const server = new WebSocket.Server({ port: 8080 });
server.on('connection', (ws) => {
ws.on('message', (data) => {
try {
const message = JSON.parse(data);
// Validation with proper close codes
if (message.text && message.text.length > 10000) {
ws.close(CLOSE_CODES.MESSAGE_TOO_BIG, 'Message exceeds 10KB limit');
return;
}
if (message.type === 'admin' && !isAdmin(message.token)) {
ws.close(CLOSE_CODES.POLICY_VIOLATION, 'Admin access required');
return;
}
processMessage(ws, message);
} catch (error) {
ws.close(CLOSE_CODES.INVALID_PAYLOAD, 'Invalid JSON');
}
});
});
// Client handling close codes
const client = new WebSocket('wss://server.example.com/ws');
client.onclose = (event) => {
switch (event.code) {
case 1000:
console.log('Normal closure');
break;
case 1001:
console.log('Server going away, will reconnect');
break;
case 1008:
console.log('Policy violation:', event.reason);
showAuthError(event.reason);
break;
case 1009:
console.log('Message too large, reducing payload size');
reduceChunkSize();
break;
case 1011:
console.log('Server error, retrying later');
scheduleReconnect(5000);
break;
case 1012:
case 1013:
console.log('Server restarting, retrying...');
scheduleReconnect(1000);
break;
default:
if (event.code === 1006) {
console.log('Abnormal closure, reconnecting');
reconnect();
}
}
};
Expected output: Server uses appropriate close codes for different error scenarios, and client handles each code with specific recovery logic.
Common Mistakes
| Mistake | Explanation |
|---|---|
| Not handling onerror events | Unhandled errors cause uncaught exceptions and crash the process |
| Using generic error messages | Include error codes and categories so clients can respond appropriately |
| Not logging error context | Log IP addresses, user IDs, and message types with errors for debugging |
| Leaking sensitive info in errors | Do not expose stack traces, SQL queries, or internal paths in error messages |
| Setting error-based reconnect loops | Track error counts and use backoff to prevent infinite reconnect loops |
Practice Questions
- What are the standard WebSocket close codes and their meanings?
- How do you handle JSON parsing errors in WebSocket messages?
- What is the difference between onerror and onclose events?
- How do you implement graceful error recovery on the client?
- How do you log WebSocket errors effectively?
Challenge
Build a WebSocket server with comprehensive error handling that categorizes all errors, logs them with structured context, reports metrics to a monitoring system, and sends appropriate close codes with meaningful reasons.
FAQ
Mini Project
Build a WebSocket error monitoring dashboard. The server sends structured error events to a monitoring endpoint. The dashboard displays error rates, categories, close code distributions, and per-client error histories. Include alerting for specific error thresholds.
What's Next
Learn about WebSocket security best practices
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro