Understand how WebSocket and Socket.IO work and which option suits old-school JavaScript.
The native WebSocket interface opens a full-duplex channel between browser and server, eliminating HTTP polling latency. It is stable and widely supported, including IE10+ and any ES5-capable browser.
Socket.IO wraps WebSockets with automatic fall-backs (long-polling, JSONP) and higher-level features such as rooms, namespaces, and automatic reconnection. Official docs confirm support down to IE9, but later client builds require ES2015 syntax, so pinning to socket.io-client ~2.2.0
keeps pure ES5 output.
Aspect | WebSocket API + ws | Socket.IO v2.2 |
---|---|---|
Browser runtime | Native ES5, IE10+ | Bundled ES5 script, IE9+ |
Fallbacks | None | XHR-long-polling if WS blocked |
Server install | npm i ws | npm i socket.io |
Extra features | Ping/pong, per-message deflate | Rooms, namespaces, acknowledgments |
Bundle size (gzip) | 0 KB (browser native) | ≈15 KB |
Lightweight, fast, zero dependencies – perfect for Node v0.10 → v22.
Run npm install ws
. The package is a pure-JS implementation that passes the full Autobahn test-suite and supports per-message-deflate compression by default.
// server.js (ES5)
var WebSocketServer = require('ws').Server;
var wss = new WebSocketServer({ port: 8080 });
wss.on('connection', function (ws) {
ws.on('message', function (msg) { ws.send(msg); });
ws.send('Hello client!');
});
function broadcast(data) {
wss.clients.forEach(function each(client) {
if (client.readyState === client.OPEN) { client.send(data); }
});
}
Call ws.isAlive = true
on each pong, then iterate wss.clients every 30 s and terminate dead sockets. This prevents phantom connections in flaky networks.
// wss.js — HTTPS upgrade
var https = require('https');
var fs = require('fs');
var WebSocketServer = require('ws').Server;
var server = https.createServer({
cert: fs.readFileSync('./cert.pem'),
key : fs.readFileSync('./key.pem')
}).listen(8443);
var wss = new WebSocketServer({ server: server });
Use pm2 start server.js -i max --watch
for cluster mode. PM2 balances TCP connections across workers. Sticky-sessions are unnecessary because WebSocket hand-off happens after the HTTP upgrade completes on each worker.
Works on any browser shipping the WebSocket interface (IE10+, BB10 2015).
// client.js — attach after DOM ready
var ws = new WebSocket('ws://'+location.host+':8080');
ws.onopen = function () { log('open'); ws.send('Hi server'); };
ws.onmessage = function (ev) { log('got: '+ ev.data); };
ws.onclose = function () { log('closed'); };
function log(txt){ document.getElementById('out').innerHTML += txt + '
'; }
function Reconnect(url, delay){
var sock; var timer;
function connect(){
sock = new WebSocket(url);
sock.onopen = function(){ clearTimeout(timer); };
sock.onclose = function(){ timer = setTimeout(connect, delay); };
sock.onmessage = function(e){ /* … */ };
}
connect();
}
Reconnect('ws://'+location.host+':8080', 2000);
Send strings like { "type":"chat","payload":"hello" }
to allow feature-switching without breaking older clients.
Feature-rich solution for ES5 when pinned to v2.2.x.
Versions ≥2.3 include a debug 4.x dependency which introduces const
; pin to ~2.2.0
in package.json or transpile.
// io-server.js
var http = require('http').createServer();
var io = require('socket.io')(http);
io.on('connection', function (socket) {
socket.emit('hello', 'welcome');
socket.on('chat', function (msg) { io.emit('chat', msg); });
});
http.listen(8080);
Call socket.join('room1') to group clients, then broadcast with io.to('room1').emit('event', data). Namespaces are created by prefixing the path: io.of('/admin')
.
The client automatically retries up to 5
times with exponential back-off; configure through reconnectionAttempts, reconnectionDelay.
Protect the channel and keep your server healthy.
Always serve over TLS (wss) in production. Use LetsEncrypt or place Node behind an Nginx reverse proxy terminating SSL.
Validate the Origin header in WebSocket upgrade requests. For ws, check req.headers.origin
; for Socket.IO, set cors policy on the server constructor.
Pass a signed JWT as a URL query (?token=…
) or in the initial HTTP cookies, then verify in your connection handler before accepting the socket.
Track message counts per socket / IP. Disconnect aggressive peers and throttle broadcast loops.
On every close, clear timers and erase socket references to prevent memory leaks.
Trace packets and reproduce edge-cases.
For ws, attach ws.on('error', console.error)
and run Node with NODE_DEBUG=net,tls
. For Socket.IO, set localStorage.debug = 'socket.io-client:*'
in the browser console.
Use Chrome DevTools → Network conditions or tc on Linux to inject latency and packet loss, confirming your reconnection code works.
Write Node tests with ws
client instances that send prerecorded JSON frames to ensure backward compatibility whenever you change message formats.