1. Basics
1. β What are WebSockets?
Section titled β1. β What are WebSockets?βWebSockets provide a full-duplex, persistent communication channel over a single TCP connection between a client (like a browser) and a server.
π Key Features of WebSockets:
Section titled βπ Key Features of WebSockets:β- Real-time Communication: Ideal for chat apps, games, live notifications.
- Persistent Connection: Unlike HTTP, where a new request is made every time, WebSocket stays open.
- Bidirectional: Server can push messages to client without client requesting them.
- Lightweight: Less overhead compared to repeated HTTP polling or long polling.
π§° What is Socket.IO?
Section titled βπ§° What is Socket.IO?βSocket.IO is a JavaScript library that simplifies using WebSockets by:
- Adding fallbacks (like long polling) if WebSocket isnβt supported.
- Handling reconnections, event handling, and rooms/channels.
2. π οΈ Comprehensive Guide
Section titled β2. π οΈ Comprehensive Guideβ2.1 π¦ Install Dependencies
Section titled β2.1 π¦ Install Dependenciesβnpm init -ynpm install express socket.io2.2 Set Up a Socket.IO Server
Section titled β2.2 Set Up a Socket.IO Serverβindex.js
Section titled βindex.jsβconst express = require('express');const http = require('http');const { Server } = require('socket.io');
const app = express();const server = http.createServer(app); // Create a raw HTTP serverconst io = new Server(server); // Bind Socket.IO to it
app.get('/', (req, res) => { res.sendFile(__dirname + '/index.html');});
// Handle socket connectionio.on('connection', (socket) => { console.log('A user connected');
socket.on('chat message', (msg) => { console.log('Message: ' + msg); io.emit('chat message', msg); // broadcast to all clients });
socket.on('disconnect', () => { console.log('A user disconnected'); });});
server.listen(3000, () => { console.log('Server running at http://localhost:3000');});2.3 Client HTML (for testing)
Section titled β2.3 Client HTML (for testing)βindex.html
Section titled βindex.htmlβ<!DOCTYPE html><html> <head> <title>Socket.IO Chat</title> </head> <body> <ul id="messages"></ul> <form id="form" autocomplete="off"> <input id="input" /><button>Send</button> </form>
<script src="/socket.io/socket.io.js"></script> <script> const socket = io(); // Connect to server
const form = document.getElementById('form'); const input = document.getElementById('input'); const messages = document.getElementById('messages');
form.addEventListener('submit', (e) => { e.preventDefault(); if (input.value) { socket.emit('chat message', input.value); input.value = ''; } });
socket.on('chat message', function(msg) { const item = document.createElement('li'); item.textContent = msg; messages.appendChild(item); window.scrollTo(0, document.body.scrollHeight); }); </script> </body></html>When you donβt specify IP/port:
Section titled βWhen you donβt specify IP/port:βconst socket = io();- This tries to connect to the same host and port that served the HTML page.
- It uses the current browser URLβs origin (like
http://localhost:3000), so it works fine if your Express app and Socket.IO are served from the same place.
When you do need to specify IP/port:
Section titled βWhen you do need to specify IP/port:βconst socket = io('http://localhost:3000');Use this only if:
- The client and server are on different origins (e.g., server is hosted separately).
- Youβre serving the frontend separately (e.g., from a different React/Vite dev server).
β Summary:
Section titled ββ Summary:β| Situation | io() is enough? |
|---|---|
| Frontend served by Express (same port) | β Yes |
| Frontend on different port or domain | β No β use io('http://host:port') |
2.4 π‘ Core Socket.IO Concepts
Section titled β2.4 π‘ Core Socket.IO Conceptsβ1. π€ Emit and Receive Events
Section titled β1. π€ Emit and Receive Eventsβsocket.emit('event-name', data); // to one clientio.emit('event-name', data); // to all clientssocket.broadcast.emit('event-name', data); // to everyone except sender (sender means the publisher who publish the messages to the topic/event-name)2. π‘ Namespaces
Section titled β2. π‘ NamespacesβUsed to create multiple communication channels over one connection.
const chat = io.of('/chat');chat.on('connection', socket => { ... });3. π Rooms
Section titled β3. π RoomsβUsed for grouping sockets (e.g., private rooms in chat).
socket.join('room1');io.to('room1').emit('message', 'hello room1');4. π Handling Disconnections, Errors, and Reconnects
Section titled β4. π Handling Disconnections, Errors, and Reconnectsβsocket.on('disconnect', reason => { console.log('Disconnected:', reason);});
socket.on('connect_error', err => { console.error('Connection Error:', err);});5. π Broadcasting to Others
Section titled β5. π Broadcasting to Othersβsocket.broadcast.emit('msg', 'User joined'); // exclude sender (publisher)5.1 How Socket.IO actually works under the hood?
Section titled β5.1 How Socket.IO actually works under the hood?βWhen you use socket.broadcast.emit(...), Socket.IO internally keeps a list of all active sockets, and it just sends the event to all of them except the one represented by socket (i.e., the current connection that triggered the event).
π§ How Socket.IO knows which sockets to send to:
Section titled βπ§ How Socket.IO knows which sockets to send to:β-
All connected sockets are stored in memory
-
Internally, Socket.IO maintains a map of all active sockets in the server using something like:
io.sockets.sockets // a Map of socket.id -> socket
-
-
When you call:
socket.broadcast.emit('chat message', msg);Socket.IO does this:
- Iterates over all connected sockets
- Skips the current
socket - Emits
'chat message'to everyone else
π§ͺ Hereβs a way to see it yourself:
Section titled βπ§ͺ Hereβs a way to see it yourself:βio.on('connection', (socket) => { console.log('Socket connected:', socket.id);
socket.on('chat message', (msg) => { console.log('Sender:', socket.id); console.log('All sockets:', [...io.sockets.sockets.keys()]);
socket.broadcast.emit('chat message', msg); });});This will print something like:
Sender: HJks8Zx7G1All sockets: [ 'HJks8Zx7G1', 'IUd7Hs9Wd2', 'AksP02HeqX' ]So Socket.IO knows: βI should send this to IUd7Hs9Wd2 and AksP02HeqX, but not HJks8Zx7G1.β
5.2 What if youβre using rooms?
Section titled β5.2 What if youβre using rooms?β- Socket.IO supports rooms and namespaces to control who gets a message.
- Even in that case,
socket.broadcast.to(room).emit(...)will only broadcast to the room excluding the sender.
5.2 β Summary:
Section titled β5.2 β Summary:βsocket.broadcastis built into Socket.IO.- It uses the internal list of all sockets.
- It automatically skips the current one (
socket). - No need for you to track who else is connected β unless you want finer control (e.g., by user ID).
6. π Authentication (Basic)
Section titled β6. π Authentication (Basic)βYou can send auth info when connecting:
// clientconst socket = io({ auth: { token: "123" }});
// serverio.use((socket, next) => { const token = socket.handshake.auth.token; if (token === "123") next(); else next(new Error("unauthorized"));});7. π¦ Bonus: Serve Static Files with Express
Section titled β7. π¦ Bonus: Serve Static Files with Expressβapp.use(express.static('public')); // Place client files in 'public' directory8. π§ͺ Testing
Section titled β8. π§ͺ TestingβUse the browser console (console.log), or tools like:
- Postman (for HTTP)
- Browser DevTools (for WS inspection under Network tab)
socket.io-clientin Node.js (for automated tests)
3. π§ What is a Namespace?
Section titled β3. π§ What is a Namespace?βA namespace in Socket.IO lets you create separate communication channels on the same connection β useful for separating concerns (e.g., /chat, /admin).
π File: server.js
Section titled βπ File: server.jsβconst express = require('express');const http = require('http');const { Server } = require('socket.io');
const app = express();const server = http.createServer(app);const io = new Server(server);
// Default namespace ("/")io.on('connection', (socket) => { console.log('User connected to default namespace');});
// Chat namespaceconst chatNamespace = io.of('/chat');
chatNamespace.on('connection', (socket) => { console.log('User connected to /chat namespace');
socket.on('message', (msg) => { console.log('Chat message:', msg); chatNamespace.emit('message', msg); // Broadcast to all in /chat });
socket.on('disconnect', () => { console.log('User disconnected from /chat'); });});
server.listen(3000, () => { console.log('Server running at http://localhost:3000');});π File: public/chat.html
Section titled βπ File: public/chat.htmlβ<!DOCTYPE html><html> <head> <title>Chat Namespace</title> </head> <body> <h1>Chat</h1> <ul id="messages"></ul> <input id="input" /><button onclick="send()">Send</button>
<script src="/socket.io/socket.io.js"></script> <script> const socket = io('/chat'); // Connect to /chat namespace
socket.on('message', function (msg) { const li = document.createElement('li'); li.textContent = msg; document.getElementById('messages').appendChild(li); });
function send() { const input = document.getElementById('input'); socket.emit('message', input.value); input.value = ''; } </script> </body></html>π Serve the client
Section titled βπ Serve the clientβAdd this to server.js to serve static files:
app.use(express.static('public'));Make sure chat.html is saved in a public folder.
π Result:
Section titled βπ Result:β- Client connects to
http://localhost:3000/chat.html - Uses namespace
/chat - Messages are isolated from other namespaces (like
/or/admin)