This tutorial covers the basic concepts of Socket.IO. By the end, you’ll understand when sockets are useful, and how to emit and listen for events for real‑time updates.
Contents
What is Socket.IO?
Socket.IO is a library that enables real‑time, bidirectional, persistent communication between client(s) and server(s). Bidirectional means both the client and server can send messages at any time. These features make Socket.IO ideal for live dashboards, chats, multiplayer games, and collaborative tools.
How Does It Work?
Socket.IO follows the observer/listener pattern:
- Publisher — where
.emit(...)
is called; it sends data on a channel (event name). - Subscriber — where
.on(...)
is used; it listens for events and runs a handler.
Under the hood, Socket.IO uses WebSockets (with fallbacks) to maintain a persistent connection.
Socket.IO vs. REST APIs
Feature | Socket.IO | REST API |
---|---|---|
Design Pattern | Bidirectional, Publisher–Subscriber | Request–Response, client‑initiated |
Connection | Persistent (WebSocket or fallback) | Per‑request (HTTP) |
Use Cases | Real‑time, low‑latency updates | CRUD, on‑demand data fetch |
Data Flow | Either side can send anytime | Client pulls; server responds |
Statefulness | Often stateful | Stateless |
Example Uses:
Socket.IO is great for any use case where real-time updates are essential, or when the client and server need continuous communication. A few examples are:
-
Chat Room – This is a simple use case outlined in the documentation (linked below). Users need to send and receive messages in real time (instantly). With Socket.IO, the user can emit a message to the server, which then broadcasts it to the other connected users. If you were to use a REST API here, there would be a lot of overhead and latency in sending/receiving messages due to the need for constant requests to the API.
-
Multiplayer Games – Real-time game state sharing with low latency is essential for smooth multiplayer gameplay. By emitting socket events with the updated game state, you can ensure that all connected player clients have the same synchronized copy of the game state to display.
-
Collaborative Tools – For applications like collaborative text editors or whiteboards, sockets can help keep the state synchronized across clients. When a user makes a change, the change will be emitted to the server, which may internally update the “source of truth” for the application. Then, the updated state would be emitted to all other connected clients, so that everyone sees the edits in real time.
Using Socket.IO
Starter Files (Express 5 + TS ESM)
Why this section? The previous tutorial let learners install packages ad‑hoc, which pulled the latest Express (v5) but showed code written for older setups. To avoid version mismatches, start from the following files and then run
npm install
.
Create the four files below in your project root:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Socket.IO Tutorial</title>
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<h1>Hello Socket.IO!</h1>
<script>
const socket = io();
socket.on("connect", () => console.log("Connected to the server"));
socket.on("disconnect", () => console.log("Disconnected from the server"));
</script>
</body>
</html>
package.json
{
"name": "socketio-tutorial",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"dev": "ts-node --esm server.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^5.1.0",
"socket.io": "^4.8.1"
},
"devDependencies": {
"@types/express": "^5.0.3",
"@types/node": "^22.5.0",
"@types/socket.io": "^3.0.1",
"ts-node": "^10.9.2",
"typescript": "^5.9.2"
}
}
Notes:
- We keep Express 5 and Socket.IO 4 pinned here.
- We run in ESM mode and use
ts-node --esm
for development.
tsconfig.json
{
"compilerOptions": {
"target": "es2022",
"module": "nodenext",
"moduleResolution": "nodenext",
"lib": ["es2022"],
"types": ["node"],
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
},
"include": ["./**/*.ts"],
"exclude": ["node_modules"]
}
server.ts
Important: In ESM,
__dirname
is not defined. Useimport.meta.url
to derive it. ```ts import express from “express”; import type { Request, Response } from “express”; import http from “http”; import { Server } from “socket.io”; import type { Socket } from “socket.io”; import { fileURLToPath } from “url”; import { dirname, join } from “path”;
const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename);
const app = express(); const server = http.createServer(app); const io = new Server(server);
app.get(“/”, (req: Request, res: Response) => { res.sendFile(join(__dirname, “index.html”)); });
io.on(“connection”, (socket: Socket) => { console.log(“A user connected”); socket.on(“disconnect”, () => { console.log(“A user disconnected”); }); });
const PORT = 3000;
server.listen(PORT, () => {
console.log(Server is running on http://localhost:${PORT}
);
});
## Set Up a New Project
1. **Create a project and add the files**
```bash
mkdir socketio-tutorial && cd socketio-tutorial
# Create index.html, package.json, tsconfig.json, server.ts exactly as above
- Install packages
npm install
- Run the dev server
npm run dev
- Open
http://localhost:3000
and check your browser console for the connection logs.
Emitting and Receiving Socket Events
1) Creating sockets on client and server
Client (in index.html
):
<script>
const socket = io();
socket.on("connect", () => console.log("Connected"));
</script>
Server (in server.ts
):
io.on("connection", (socket) => {
console.log("A user connected");
});
2) Emitting a custom event
Client → Server:
<script>
// Send a chat message
socket.emit("chat message", { user: "John", message: "Hello, World!" });
</script>
Server listens:
io.on("connection", (socket) => {
socket.on("chat message", (data: { user: string; message: string }) => {
console.log(`${data.user}: ${data.message}`);
});
});
3) Broadcasting to all clients
Server → All clients:
io.emit("chat message", { user: "Server", message: "Welcome to the chat!" });
Client listens:
<script>
socket.on("chat message", (data) => {
console.log(`${data.user}: ${data.message}`);
});
</script>
Putting It All Together
Server (server.ts
):
io.on("connection", (socket) => {
console.log("A user connected");
socket.on("chat message", (data: { user: string; message: string }) => {
// Re-broadcast to everyone
io.emit("chat message", data);
});
socket.on("disconnect", () => {
console.log("A user disconnected");
});
});
Client (index.html
):
<script>
const socket = io();
socket.on("chat message", (data) => {
console.log(`${data.user}: ${data.message}`);
});
// Example: immediately emit something
socket.emit("chat message", { user: "John", message: "Hello, World!" });
</script>
Troubleshooting
ReferenceError: __dirname is not defined
— You’re in ESM. Use thefileURLToPath(import.meta.url)
pattern shown above.- Type errors for Node globals — Install
@types/node
(already listed) and ensure"types": ["node"]
is set intsconfig.json
. - Wrong Express/Socket.IO versions — Don’t install ad‑hoc. Use the provided
package.json
and runnpm install
so versions match this tutorial.