WebSockets & Server-Sent Events
With fetch, the browser always speaks first: it asks, the server answers, and the conversation ends. That is fine for loading a product list, but not for a live cricket score, a chat app, a delivery partner moving on a map or a share price that changes every second. For these, the server needs a way to send new data the moment it has it.
In this lesson you will learn two browser APIs for live data: WebSockets, a two-way connection that stays open, and Server-Sent Events, a simpler one-way stream from server to browser. You will also see the older polling techniques they replaced.
Ways to get live updates
| Technique | How it works | Direction | Status |
|---|---|---|---|
| Polling | Ask with fetch every few seconds | Browser asks | Simple but wasteful |
| Long polling | The server holds each request open until it has news | Browser asks | Older workaround |
| Server-Sent Events | One long HTTP response that keeps sending events | Server to browser | Current, simple |
| WebSocket | A permanent two-way connection | Both ways | Current, flexible |
Polling with setInterval still works for slow-changing data, such as checking an exam result page every minute. But most of its requests come back with "nothing new", and updates arrive late. WebSockets and Server-Sent Events push updates instantly over one connection.
Opening a WebSocket
A WebSocket starts as a normal HTTP request and then "upgrades" into a connection that stays open. Either side can send a message at any time. WebSocket addresses start with wss:// (secure, like https://) or ws:// (unencrypted). Always use wss:// on real sites.
const socket = new WebSocket("wss://example.com/live");The constructor returns immediately while the connection is made in the background. You react to four events:
| Event | Fires when |
|---|---|
open | The connection is ready; you can now send |
message | The server sent something; it is in event.data |
error | Something went wrong (the event gives no details, for security) |
close | The connection has ended; event.code and event.reason say why |
The readyState property tells you where the connection is: 0 CONNECTING, 1 OPEN, 2 CLOSING, 3 CLOSED.
This example connects to a public echo server, a test server that sends back every message it receives. It is run by a third party, so if it happens to be down you will see the error message instead.
const socket = new WebSocket("wss://ws.postman-echo.com/raw");
console.log("readyState:", socket.readyState); // 0 = CONNECTING
socket.addEventListener("open", () => {
console.log("Connected, readyState:", socket.readyState);
socket.send("Namaste from Pune");
socket.send(JSON.stringify({ type: "score", runs: 146, wickets: 3 }));
});
let received = 0;
socket.addEventListener("message", (event) => {
console.log("Server sent:", event.data);
received++;
if (received === 2) socket.close(1000, "Finished");
});
socket.addEventListener("close", (event) => {
console.log("Closed with code", event.code, "clean:", event.wasClean);
});
socket.addEventListener("error", () => {
console.log("Could not connect (the test server may be down)");
});readyState: 0
Connected, readyState: 1
Server sent: Namaste from Pune
Server sent: {"type":"score","runs":146,"wickets":3}
Closed with code 1000 clean: truesend() accepts a string, a Blob or binary data such as an ArrayBuffer. Objects must be turned into text with JSON.stringify() first. Calling send() before the open event throws an InvalidStateError, so always send from inside the open listener or check socket.readyState === WebSocket.OPEN.
Designing messages
A WebSocket carries plain messages with no built-in meaning, so apps agree on a small format. The common choice is JSON with a type field that says what kind of message it is:
const socket = new WebSocket("wss://chat.example.com/rooms/class-10b");
socket.addEventListener("open", () => {
socket.send(JSON.stringify({ type: "join", name: "Meera" }));
});
socket.addEventListener("message", (event) => {
let msg;
try {
msg = JSON.parse(event.data);
} catch {
return; // ignore anything that is not valid JSON
}
switch (msg.type) {
case "chat":
showMessage(msg.from, msg.text); // use textContent inside, never innerHTML
break;
case "typing":
showTyping(msg.from);
break;
case "left":
showNotice(`${msg.from} left the room`);
break;
}
});
function sendChat(text) {
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({ type: "chat", text }));
}
}Treat every incoming message as untrusted: parse it inside try, check its type, and insert text with textContent so a message cannot inject HTML into your page (see Best Practices & Security).
For binary messages, such as audio chunks or game state, set socket.binaryType = "arraybuffer" to receive an ArrayBuffer instead of the default Blob (see Typed Arrays, Blobs & Base64).
Closing and reconnecting
Call socket.close(code, reason) when you are finished. Code 1000 means a normal close; you may also use your own codes from 3000 to 4999. The close event reports the code the connection ended with:
| Code | Meaning |
|---|---|
1000 | Normal closure |
1001 | Going away, for example the server is restarting or the user left the page |
1006 | Closed abnormally with no close message, for example the network dropped |
1011 | The server hit an unexpected error |
Connections drop all the time on mobile networks, so real apps reconnect automatically. Wait a little longer after each failure (this is called exponential backoff) so that thousands of users do not all hammer a recovering server at the same moment:
function connect(attempt = 0) {
const socket = new WebSocket("wss://example.com/live-score");
socket.addEventListener("open", () => {
attempt = 0; // connected, so reset the counter
});
socket.addEventListener("message", (event) => {
updateScoreboard(JSON.parse(event.data));
});
socket.addEventListener("close", (event) => {
if (event.code === 1000) return; // we closed it on purpose
const delay = Math.min(1000 * 2 ** attempt, 30000); // 1s, 2s, 4s ... max 30s
setTimeout(() => connect(attempt + 1), delay);
});
}
connect();WebSockets need a server that understands the WebSocket protocol. In Node.js the ws package is a common choice, and many developers use the Socket.IO library, which adds rooms and automatic reconnection. Note that Socket.IO uses its own protocol on top, so a Socket.IO server needs the Socket.IO client, not a plain WebSocket.
Server-Sent Events
Often you only need updates in one direction: scores, notifications, progress of a long job, or text appearing word by word from an AI chatbot. Server-Sent Events (SSE) are made for this. The browser opens a normal HTTP request with EventSource, and the server keeps the response open, writing events into it as they happen.
The server sends plain text with the content type text/event-stream. Each event is one or more field: value lines followed by a blank line:
id: 1
data: {"over": 17.1, "runs": 142, "wickets": 3}
id: 2
data: {"over": 17.2, "runs": 146, "wickets": 3}
id: 3
event: wicket
data: {"batter": "Kabir", "out": "caught"}
: a line starting with a colon is a comment
id: 4
data: {"over": 17.4, "runs": 146, "wickets": 4}The data field holds the message, event gives it a name (without one, it is a message event), id labels it, and an optional retry line tells the browser how many milliseconds to wait before reconnecting. On the page:
const source = new EventSource("/live-score");
source.addEventListener("message", (event) => {
const score = JSON.parse(event.data);
console.log(`Over ${score.over}: ${score.runs}/${score.wickets}`);
});
source.addEventListener("wicket", (event) => {
const { batter, out } = JSON.parse(event.data);
console.log(`WICKET! ${batter} is ${out}`);
});
source.addEventListener("error", () => {
console.log("Connection lost, readyState", source.readyState);
});With the stream above, the console shows:
Over 17.1: 142/3 Over 17.2: 146/3 WICKET! Kabir is caught Over 17.4: 146/4
Named events need addEventListener with their own name; the message listener does not receive them. The best feature of EventSource is that reconnection is built in. If the connection drops, the browser waits and reconnects by itself (readyState goes back to 0 while it tries), and it sends the last id it saw in a Last-Event-ID request header, so the server can continue from where the user left off. Call source.close() to stop for good.
EventSource only makes GET requests and cannot set custom headers, but it sends cookies for the same site, so normal login sessions work. It sends text only, not binary data.
Choosing between them
- Use Server-Sent Events when only the server talks: live scores, notifications, dashboards, streamed AI replies. They work with ordinary HTTP servers, proxies and logins, and they reconnect automatically.
- Use WebSockets when both sides talk often and quickly: chat, multiplayer games, collaborative editing, live cursors.
- Use plain
fetch(with polling, if needed) when updates are rare.
Common mistakes
- Sending before the connection opens. Wait for the
openevent. - Sending objects directly.
socket.send({ a: 1 })sends the text"[object Object]"; useJSON.stringify(). - Trusting incoming data. Parse inside
try, check the message type, and never put it intoinnerHTML. - Not reconnecting. WebSockets do not reconnect on their own;
EventSourcedoes. - Expecting a
messagelistener to get named SSE events. Listen for each event name. - Using
ws://on anhttps://page. Browsers block it as mixed content; usewss://.
Key points
new WebSocket("wss://...")opens a two-way connection; use theopen,message,errorandcloseevents.send()sends strings or binary data; send JSON with atypefield and parse it on arrival.- Close with
socket.close(1000)and reconnect after unexpected closes with increasing delays. new EventSource(url)receives a one-way stream of server events over plain HTTP.- SSE events have
data, optionaleventnames andids, and reconnect automatically withLast-Event-ID. - Choose SSE for server-to-browser updates and WebSockets for frequent two-way messages.
Exercise
Using the echo server from the lesson (wss://ws.postman-echo.com/raw), write a tiny "chat" in the Try-it editor. When the connection opens, send three JSON messages from three friends (Aarav, Isha and Rohan) with a type of "chat". For each message that comes back, parse it and print Name: text. After all three have returned, close the connection with code 1000 and print "Chat closed".
Show solution
const socket = new WebSocket("wss://ws.postman-echo.com/raw");
const outgoing = [
{ type: "chat", from: "Aarav", text: "Match at 4 pm?" },
{ type: "chat", from: "Isha", text: "Yes, at the Rohtak stadium" },
{ type: "chat", from: "Rohan", text: "I will bring the ball" },
];
let count = 0;
socket.addEventListener("open", () => {
for (const msg of outgoing) socket.send(JSON.stringify(msg));
});
socket.addEventListener("message", (event) => {
const msg = JSON.parse(event.data);
if (msg.type === "chat") console.log(`${msg.from}: ${msg.text}`);
if (++count === outgoing.length) socket.close(1000);
});
socket.addEventListener("close", () => console.log("Chat closed"));
socket.addEventListener("error", () => console.log("Could not connect"));Aarav: Match at 4 pm? Isha: Yes, at the Rohtak stadium Rohan: I will bring the ball Chat closed
All three sends happen inside the open listener, so none are sent too early. Counting the replies tells the code when every message has come back.
Quick Quiz
1. Which API gives a two-way connection where both browser and server can send at any time?
A WebSocket stays open and either side may send messages; Server-Sent Events only go from server to browser.
2. What happens if you call socket.send() before the open event?
The connection is still in the CONNECTING state, so sending is not allowed yet.
3. Which feature does EventSource have that WebSocket does not?
The browser reconnects an EventSource by itself and tells the server the last event id it received.
4. A server sends an SSE event with the line event: wicket. How do you receive it?
Named events are delivered only to listeners registered for that name.
Learn JavaScript with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
