This post contains a flashing gif.
HTTP requests have taken me pretty far, but I’m starting to run into their limits. How do I tell a client that the server updated at midnight, and it needs to fetch the newest data? How do I notify one user when another user makes a post? In short, how do I get information to the client without it initiating the request?
Websockets
One possible solution is to use websockets, which establish a persistent connection between the client and server. This will allow us to send data to the client when we want to, without waiting for the client’s next request. Websockets have their own protocol (though the connection is initiated with HTTP requests) and are language-agnostic. We could, if we wanted, implement a websocket client and its corresponding server from scratch or with Deno… or we could use one of the libraries that’s already done the hard work for us. I’ve used Socket.IO in a previous project, so we’ll go with that. I enjoyed working with it before, and it even has the advantage of a fallback in case the websocket fails.
Colorsocket
For immediate visual feedback, we’ll make a small demo where any one client can affect the colors displayed on all. Each client on the /color endpoint has a slider to control one primary color, plus a button to invert all the other /color clients. (The server assigns a color in order to each client when the client connects, so you just have to refresh a few times until you get all three colors. I did make sure duplicate colors would work in sync, however.) The /admin user can turn primary colors on or off. Here’s the app in action:
The clients aren’t all constantly making requests to the server. How do they know to update?
Establishing Connections
When each client runs its <script>, it creates a new socket, which opens a connection to the server.
// color.html
const socket = io('/color'); // we’ll come back to the argument
The script then assigns handlers on the new socket for the various events we expect to receive from the server:
// color.html
socket.on('assign-color', (color, colorSettings, activeSettings) => {
document.getElementById('color-name').innerText = color;
controllingColor = color;
currentBackground = colorSettings;
active = activeSettings;
colorSlider.disabled = !active[controllingColor];
document.getElementById('active').innerText = active[controllingColor] ? 'active' : 'inactive';
colorSlider.value = colorSettings[controllingColor];
updateBackground();
});
socket.on('set-color', (color, value) => {
currentBackground[color] = value;
if (controllingColor === color) {
colorSlider.value = value;
}
updateBackground();
});
socket.on('invert', () => {
inverted = !inverted;
document.getElementById('inverted').innerText = inverted ? '' : 'not ';
updateBackground();
});
socket.on('toggle-active', (color) => {
active[color] = !active[color];
if (controllingColor === color) {
colorSlider.disabled = !active[color];
}
document.getElementById('active').innerText = active[controllingColor] ? 'active' : 'inactive';
updateBackground();
});
Meanwhile, the server detects the new connection. It assigns the client a color, sends that color and current state of the application to the client, and sets up its own handlers for events received through the socket:
// index.js
colorNamespace.on('connection', (socket) => {
const color = colors[colorCount % 3]; // pick the next color in the list, then loop
colorCount++;
socket.emit('assign-color', color, colorSettings, activeSettings); // synchronize the client with the application state
socket.data.color = color; // you can save information to a socket’s data key, but I didn’t end up using this for anything
socket.on('set-color', (color, value) => {
colorSettings[color] = value;
colorNamespace.emit('set-color', color, value);
});
socket.on('invert', () => {
socket.broadcast.emit('invert');
});
});
The /admin page follows similar setup.
Sending Information to the Client
Let’s follow how user interaction on one page changes all the others.
When a user on the blue page moves the slider, the slider emits a change event, which is caught by the slider’s event listener:
// color.html
colorSlider.addEventListener('change', (event) => {
socket.emit('set-color', controllingColor, event.target.value);
});
That event listener emits a new set-color event with the color and new value. The server receives the client’s set-color, then emits its own to transmit that data to all clients. Each client receives the message and updates its blue value accordingly.
Broadcasting to Other Sockets
But clicking the “Invert others” button affects the other /color users, but not the user who actually clicked the button! The key here is the broadcast flag when the server receives and retransmits the invert message:
// server.js
socket.on('invert', () => {
socket.broadcast.emit('invert'); // broadcast
});
This flag means that that the server will send the event to every socket except the one it’s called on. Here this is just a neat trick, but in practice, it might be useful to avoid sending a post to the user who originally wrote it, because their client already has that information.
Namespaces
You may have noticed that the admin tab isn’t changing color with the other three. For simplicity, I didn’t set up any handlers for the admin page. But even if I had, they wouldn’t do anything, because the admin socket isn’t receiving those events at all. This is because the admin tab is in a different namespace.
// color.html
const socket = io('/color');
// =======================
// admin.html
const socket = io('/admin');
// =======================
// index.js
const colorNamespace = io.of('/color');
const adminNamespace = io.of('/admin');
…
colorNamespace.emit('set-color', color, value); // the admin page doesn’t receive this event
(For clarity, I gave my two namespaces the same names as the two endpoints the pages are located at, but I didn’t have to. The namespaces could have had arbitrary names with no change in functionality, as long as the client matched the server.)
Namespaces provide a convenient way to target a subset of sockets. However, namespaces can communicate with each other:
// admin.html
const toggleFunction = (color) => {
socket.emit('toggle-active', color);
};
// =======================
// index.js
// clicking the buttons on the admin page triggers changes on the color pages
adminNamespace.on('connection', (socket) => {
socket.on('toggle-active', color => {
activeSettings[color] = !activeSettings[color];
colorNamespace.emit('toggle-active', color);
});
});
// =======================
// color.html
socket.on('toggle-active', (color) => {
active[color] = !active[color];
if (controllingColor === color) {
colorSlider.disabled = !active[color];
}
document.getElementById('active').innerText = active[controllingColor] ? 'active' : 'inactive';
updateBackground();
});
In all of the examples, events were caused by some interaction on one of the clients. An event was emitted to the server, and a second message was emitted by the server to the appropriate clients. However, this is only a small sample of the possibilities. For example, a server could use websockets to update all clients on a regular cycle, or get information from some API and pass it on. This demo is only a small showcase of what I’ve been learning and hope to keep applying in my projects going forward.
References and Further Reading
Socket.IO, especially the tutorial, which got me up and running very quickly
Websockets on MDN – API reference and glossary, plus the articles on writing your own clients and servers (Deno version)
Cover Photo by Scott Rodgerson on Unsplash

Top comments (2)
Wow, this is an incredibly clear and practical explanation! I really appreciate how you broke down the client-server flow with Socket.IO—it makes even the trickier concepts like namespaces and broadcasting feel approachable.
Great article.
A question though: why use Socket.IO when NodeJs now has it natively built in?