The WebSocket API
The browser's WebSocket API, a Node.js server with ws, and a complete client+server chat example.
The browser WebSocket API
Every modern browser has a built-in WebSocket global — no library required for basic usage. Creating one immediately starts the connection handshake:
const socket = new WebSocket('wss://example.com/chat');
The object exposes four events you'll use in almost every WebSocket client:
socket.onopen = () => {
console.log('Connected!');
socket.send('Hello, server!');
};
socket.onmessage = (event) => {
console.log('Received:', event.data);
};
socket.onclose = (event) => {
console.log(`Connection closed (code ${event.code})`);
};
socket.onerror = (error) => {
console.error('WebSocket error:', error);
};
| Event | Fires when |
|---|---|
onopen |
The handshake completed and the connection is ready to use. |
onmessage |
A message arrived from the server. event.data holds the payload (a string, or binary data for Blob/ArrayBuffer messages). |
onclose |
The connection was closed, by either side, or by a network failure. event.code and event.reason describe why. |
onerror |
Something went wrong at the transport level. In practice this is almost always followed immediately by onclose. |
Sending data is a single method call — .send() accepts a string, or binary data (Blob, ArrayBuffer):
socket.send(JSON.stringify({ type: 'chat_message', text: 'Hello everyone!' }));
Since WebSocket messages are just strings (or bytes) with no built-in structure, JSON is the near-universal convention for giving messages shape — both sides agree on a schema like { type, ...payload } and dispatch based on type.
A minimal Node.js WebSocket server with ws
The ws package is the standard, minimal WebSocket server library for Node.js:
npm install ws
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (socket) => {
console.log('Client connected');
socket.on('message', (data) => {
console.log('Received:', data.toString());
socket.send(`Echo: ${data}`);
});
socket.on('close', () => {
console.log('Client disconnected');
});
});
console.log('WebSocket server listening on ws://localhost:8080');
Every connected client gets its own socket object inside the connection event handler — this is the server-side mirror of the browser's WebSocket object, with the same core idea: listen for message and close, and call .send() to push data out.
A complete working chat example
Server (server.js) — broadcasts every incoming message to all other connected clients:
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (socket) => {
socket.on('message', (data) => {
const message = JSON.parse(data.toString());
// Broadcast to every other connected client
for (const client of wss.clients) {
if (client !== socket && client.readyState === client.OPEN) {
client.send(JSON.stringify({ text: message.text, at: new Date().toISOString() }));
}
}
});
});
console.log('Chat server running on ws://localhost:8080');
Client (in the browser):
<input id="message-input" placeholder="Type a message..." />
<button id="send-button">Send</button>
<ul id="messages"></ul>
const socket = new WebSocket('ws://localhost:8080');
const messagesList = document.getElementById('messages');
socket.onopen = () => console.log('Connected to chat server');
socket.onmessage = (event) => {
const message = JSON.parse(event.data);
const item = document.createElement('li');
item.textContent = `[${message.at}] ${message.text}`;
messagesList.appendChild(item);
};
socket.onclose = () => console.log('Disconnected from chat server');
document.getElementById('send-button').addEventListener('click', () => {
const input = document.getElementById('message-input');
if (input.value.trim() === '') return;
socket.send(JSON.stringify({ text: input.value }));
input.value = '';
});
Open this page in two separate browser tabs, connect both, and a message sent from one tab appears in the other's message list instantly, pushed by the server — no polling, no page refresh, no request initiated by the receiving tab at all.
Common mistakes
- Calling
.send()before the connection has actually opened (onopenhasn't fired yet) —.send()throws if the socket isn't in theOPENstate. Always send from inside (or after) theonopenhandler, or checksocket.readyState === WebSocket.OPENfirst. - Forgetting that
event.datainonmessageis a raw string (or binary blob) — you mustJSON.parse()it yourself if the two sides agreed on a JSON message format; nothing parses it automatically. - Not handling
onclose/onerrorat all, leaving the UI silently frozen with no feedback if the connection drops — production clients need reconnect logic, not just the four bare event handlers.
Interview questions
Q: What are the four core events on the browser's WebSocket object?
onopen (the connection is ready), onmessage (a message arrived from the server), onclose (the connection ended, from either side or a network failure), and onerror (a transport-level failure, typically followed immediately by onclose).
Q: Does the WebSocket API give you any built-in message structure, like JSON?
No — .send() and event.data deal in raw strings (or binary data); there's no built-in schema or message typing. In practice, almost every real application agrees on its own convention, most commonly sending JSON strings with a type field the receiver switches on, and both sides must JSON.stringify()/JSON.parse() manually.