On the server side, Node.js is the most popular choice for WebSocket because it's event-driven and non-blocking — perfect for handling thousands of persistent connections. The ws library is the standard: fast, minimal, zero dependencies.
npm init -y
npm install ws
# For TypeScript:
npm install ws @types/ws
// server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws, req) => {
const clientIP = req.socket.remoteAddress;
console.log('New client:', clientIP);
console.log('Total connected:', wss.clients.size);
// Send welcome message to THIS client only
ws.send(JSON.stringify({
type: 'welcome',
message: 'Connected! 🎉',
clients: wss.clients.size,
}));
// Handle incoming messages
ws.on('message', (rawData) => {
let msg;
try {
msg = JSON.parse(rawData);
} catch {
ws.send(JSON.stringify({ type: 'error', message: 'Invalid JSON' }));
return;
}
console.log('Received:', msg);
// Echo back with server timestamp
ws.send(JSON.stringify({
type: 'echo',
original: msg,
timestamp: Date.now(),
}));
});
ws.on('close', (code, reason) => {
console.log('Client left. Code:', code, '| Reason:', reason.toString());
console.log('Remaining clients:', wss.clients.size);
});
ws.on('error', (err) => {
console.error('Socket error:', err.message);
});
});
wss.on('listening', () => {
console.log('🚀 WebSocket server running on ws://localhost:8080');
});
// index.html — inside <script> tag
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
console.log('✅ Connected!');
ws.send(JSON.stringify({ type: 'hello', text: 'Hey server!' }));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('📨 Received:', data);
};
ws.onerror = (error) => {
console.error('❌ Error:', error);
};
ws.onclose = (event) => {
console.log('🔌 Disconnected. Code:', event.code, '| Clean:', event.wasClean);
};
// Check readyState before sending:
// ws.readyState === 0 → CONNECTING
// ws.readyState === 1 → OPEN (can send)
// ws.readyState === 2 → CLOSING
// ws.readyState === 3 → CLOSED
In production, attach WebSocket to an existing HTTP server so both REST and WebSocket share port 443:
// server.js
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const app = express();
const server = http.createServer(app); // HTTP server
const wss = new WebSocket.Server({ server }); // WS on SAME port!
// Normal REST routes still work
app.use(express.json());
app.get('/health', (req, res) => res.json({ status: 'ok' }));
app.get('/api/users', (req, res) => res.json({ users: [] }));
// WebSocket connections
wss.on('connection', (ws) => {
ws.send(JSON.stringify({ type: 'connected' }));
ws.on('message', (data) => {
// handle messages...
});
});
// One port for everything
server.listen(3000, () => {
console.log('REST API → <http://localhost:3000/api>');
console.log('WebSocket → ws://localhost:3000');
});
| Value | Constant | Meaning |
|---|---|---|
| 0 | CONNECTING | Handshake in progress |
| 1 | OPEN | Ready to send and receive |
| 2 | CLOSING | Close handshake started |
| 3 | CLOSED | Connection terminated |
Always check
ws.readyState === 1before callingws.send()— sending on a non-OPEN socket throws an error.