Introduction
Real-time communication is at the heart of modern web applications—whether it’s a group chat, live notifications, or collaborative editing. Traditional HTTP requests simply can’t keep up with the low-latency, bidirectional data flow required for these features. That’s where WebSockets and Socket.io come in. In this post, you’ll learn what WebSockets are, why Socket.io makes your life easier, and how to build a robust, scalable chat feature from scratch. We’ll walk through server and client setup, room management, message broadcasting, and best practices for authentication and deployment. By the end, you’ll have a fully functional chat module ready to integrate into your next Node.js project.

Understanding WebSockets vs. HTTP
What Are WebSockets?
- Persistent connection: Unlike HTTP’s request-response model, WebSockets keep a single TCP connection open for continuous, two-way data exchange.
- Low latency: Ideal for scenarios requiring instant updates—chat, stock tickers, gaming, IoT dashboards.
- Binary & text support: WebSockets transmit both string and binary data efficiently.
Why Socket.io?
While raw WebSockets are powerful, they lack features every production app needs:
- Automatic reconnection when the connection drops.
- Multiplexing through “namespaces” to separate concerns.
- Room support for grouping sockets.
- Fallback transports (e.g., long polling) for older browsers.
- Middleware for authentication and logging.
Expert Insight:
“Socket.io abstracts away the quirks of different browsers and network conditions, letting developers focus on real-time logic rather than low-level socket handling.”
— Jane Doe, Senior Backend Engineer
Project Setup
1. Initialize Your Node.js Project
bashCopyEditmkdir chat-app
cd chat-app
npm init -y
npm install express socket.io cors
- express: Serves HTTP endpoints and static files.
- socket.io: Provides real-time, event-based communication.
- cors: Configures cross-origin resource sharing for your client.
2. Boilerplate Express Server

Create server.js
:
javascriptCopyEditconst express = require('express');
const http = require('http');
const cors = require('cors');
const socketIo = require('socket.io');
const app = express();
app.use(cors());
const server = http.createServer(app);
const io = socketIo(server, {
cors: { origin: '*' }
});
const PORT = process.env.PORT || 5000;
server.listen(PORT, () => console.log(`Server listening on port ${PORT}`));
Building the Chat Logic
3. Handling Socket Connections
Inside server.js
, add connection handlers:
javascriptCopyEditio.on('connection', (socket) => {
console.log(`New client connected: ${socket.id}`);
socket.on('joinRoom', ({ username, room }) => {
socket.join(room);
socket.username = username;
socket.room = room;
socket.to(room).emit('message', {
user: 'system',
text: `${username} has joined the room.`
});
});
socket.on('sendMessage', (message) => {
const { username, room } = socket;
io.to(room).emit('message', { user: username, text: message });
});
socket.on('disconnect', () => {
if (socket.room) {
socket.to(socket.room).emit('message', {
user: 'system',
text: `${socket.username} has left the room.`
});
}
console.log(`Client disconnected: ${socket.id}`);
});
});
connection
event: Fires when a client connects.joinRoom
custom event: Adds a user to a room and broadcasts a welcome message.sendMessage
event: Broadcasts a user’s message to everyone in the same room.disconnect
event: Cleans up and notifies peers.
4. Creating the Client
In your project root, create public/index.html
:
htmlCopyEdit<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Socket.io Chat</title>
<style>
/* Basic styles for clarity */
body { font-family: Arial, sans-serif; margin: 20px; }
#chat { border: 1px solid #ccc; padding: 10px; height: 300px; overflow-y: scroll; }
#messageForm { margin-top: 10px; }
</style>
</head>
<body>
<h1>Chat Room</h1>
<div>
<label>Username: <input id="username" /></label>
<label>Room: <input id="room" /></label>
<button id="joinBtn">Join</button>
</div>
<div id="chat"></div>
<form id="messageForm">
<input id="message" autocomplete="off" placeholder="Type a message..." />
<button>Send</button>
</form>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io();
const chat = document.getElementById('chat');
const messageForm = document.getElementById('messageForm');
const messageInput = document.getElementById('message');
let username, room;
document.getElementById('joinBtn').onclick = () => {
username = document.getElementById('username').value.trim();
room = document.getElementById('room').value.trim();
if (username && room) {
socket.emit('joinRoom', { username, room });
}
};
socket.on('message', ({ user, text }) => {
const div = document.createElement('div');
div.textContent = `${user}: ${text}`;
chat.appendChild(div);
chat.scrollTop = chat.scrollHeight;
});
messageForm.addEventListener('submit', (e) => {
e.preventDefault();
const msg = messageInput.value;
if (msg) {
socket.emit('sendMessage', msg);
messageInput.value = '';
}
});
</script>
</body>
</html>
- Static serve: In
server.js
, add: javascriptCopyEditapp.use(express.static('public'));
- Client logic: Captures username/room, joins the room, and displays incoming messages.
Advanced Features

5. User Lists & Room Data
Track active users in a room:
javascriptCopyEditconst users = {}; // { room1: [user1, user2], room2: [...] }
socket.on('joinRoom', ({ username, room }) => {
users[room] = users[room] || [];
users[room].push(username);
io.to(room).emit('roomData', { room, users: users[room] });
});
socket.on('disconnect', () => {
if (socket.room) {
users[socket.room] = users[socket.room].filter(u => u !== socket.username);
io.to(socket.room).emit('roomData', { room: socket.room, users: users[socket.room] });
}
});
On the client, listen for roomData
:
javascriptCopyEditsocket.on('roomData', ({ room, users }) => {
// Update a user list UI element
console.log(`Users in ${room}: ${users.join(', ')}`);
});
6. Message Persistence
For production, integrate a database (e.g., MongoDB) to store chat history:
javascriptCopyEdit// Example with Mongoose
const mongoose = require('mongoose');
const Message = mongoose.model('Message', new mongoose.Schema({
room: String,
user: String,
text: String,
timestamp: { type: Date, default: Date.now }
}));
// Save on sendMessage
socket.on('sendMessage', async (message) => {
const msg = await Message.create({ room: socket.room, user: socket.username, text: message });
io.to(socket.room).emit('message', msg);
});
Security & Best Practices

Authentication & Authorization
- JWT Middleware: Verify tokens before allowing a socket connection. javascriptCopyEdit
io.use((socket, next) => { const token = socket.handshake.auth.token; // verify JWT... next(); });
- Room validation: Ensure users can only join rooms they’re authorized for.
Scaling with Multiple Instances
- Redis Adapter: Share socket events across Node.js instances. bashCopyEdit
npm install socket.io-redis
javascriptCopyEditconst redisAdapter = require('socket.io-redis'); io.adapter(redisAdapter({ host: 'localhost', port: 6379 }));
Performance Tips
- Message batching: Group rapid-fire events to reduce network overhead.
- Compression: Enable
perMessageDeflate
in Socket.io options. - Namespace segregation: Use namespaces (
/chat
,/notifications
) to load only necessary logic on each page.
Conclusion
Building a chat feature with WebSockets and Socket.io empowers you to deliver lightning-fast, two-way communication in your web apps. You’ve seen how to set up a Node.js server, manage rooms, broadcast messages, and implement advanced functionality like user lists and persistence. With security measures like JWT authentication and scaling solutions such as Redis adapters, your chat service is ready for production. Integrate this module into your application, and watch engagement soar as users enjoy seamless, real-time interactions.