Sitemap

WebSockets: Real-Time Communication

8 min readMar 1, 2026

--

Press enter or click to view image in full size

When HTTP Isn’t Enough

You build a chat app. Users send messages. You poll the server every second. GET /messages. Nothing new. Poll again. Nothing. Poll again. Finally, new message. Already 1 second old. Server hit 60 times per minute. Wasteful. Slow.

You try long polling. Better but complex. Server holds connection. Waits for data. Sends response. Client immediately reconnects. Hacky. Still not truly real-time. Race conditions. Connection limits.

You want instant updates. User types, others see it immediately. New message appears instantly. Live notifications. Collaborative editing. Real-time dashboards. HTTP request-response model doesn’t work. Need persistent connection.

WebSockets solve this. Persistent bidirectional connection. Server pushes data to client. Client pushes to server. No polling. Instant updates. Chat, notifications, live updates, multiplayer games. WebSockets make it simple.

QUICK SUMMARY

What You’ll Learn: WebSockets provide persistent two-way connection between client and server. Unlike HTTP request-response, WebSocket connection stays open. Server can push data anytime. Client can send anytime. Socket.io simplifies WebSockets with automatic reconnection, room support, and fallback to polling. Server listens for connection events. Client connects with io(). Emit events with socket.emit(). Listen with socket.on(). Rooms group connections for targeted messaging. Broadcast sends to all connected clients. Namespaces separate concerns. Authentication verifies users before connection. Disconnect events handle cleanup. Use for chat apps, live notifications, collaborative tools, real-time dashboards, multiplayer games.

Why It Matters:

  • True real-time communication
  • No polling overhead
  • Instant updates
  • Bidirectional messaging
  • Scalable real-time features
  • Better user experience
  • Industry standard for real-time

Key Concepts:

  • Persistent connections
  • Event-driven communication
  • Socket.io library
  • Emit and listen pattern
  • Rooms for grouping
  • Broadcast to multiple clients
  • Namespaces for separation
  • Authentication and authorization

What You’ll Build:

  • WebSocket server with Socket.io
  • Real-time chat application
  • Online presence tracking
  • Typing indicators
  • Room-based messaging
  • Private messaging
  • Complete real-time system

Time Investment: Basic WebSockets take 2–3 hours to learn. Building real-time features takes practice.

Prerequisites:

  • Express.js basics (Post 38)
  • React fundamentals (Post 11)
  • Event-driven programming
  • Node.js knowledge (Post 37)

FULL DETAILED ARTICLE

HTTP vs WebSockets

HTTP request-response:

// Client
fetch('/api/messages') // Request
.then(res => res.json()) // Response
.then(data => console.log(data));
// One-directional
// Client asks, server responds
// Connection closes after response
// Polling required for updates

WebSocket connection:

// Client
const socket = io();
// Connection stays open
// Bidirectional
// Server can send anytime
socket.on('new-message', (message) => {
console.log(message); // Instant update
});
// Client can send anytime
socket.emit('send-message', { text: 'Hello' });

Setting Up Socket.io

Install dependencies:

# Server
npm install socket.io
# Client (React)
npm install socket.io-client

Basic server:

// server.js
const express = require('express');
const { createServer } = require('http');
const { Server } = require('socket.io');
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: {
origin: 'http://localhost:3000',
methods: ['GET', 'POST']
}
});
// Listen for connections
io.on('connection', (socket) => {
console.log('User connected:', socket.id);

// Listen for disconnect
socket.on('disconnect', () => {
console.log('User disconnected:', socket.id);
});
});
httpServer.listen(5000, () => {
console.log('Server running on port 5000');
});

Basic React client:

// App.jsx
import { useEffect, useState } from 'react';
import io from 'socket.io-client';
const socket = io('http://localhost:5000');function App() {
const [connected, setConnected] = useState(false);

useEffect(() => {
// Listen for connection
socket.on('connect', () => {
console.log('Connected:', socket.id);
setConnected(true);
});

// Listen for disconnect
socket.on('disconnect', () => {
console.log('Disconnected');
setConnected(false);
});

// Cleanup
return () => {
socket.off('connect');
socket.off('disconnect');
};
}, []);

return (
<div>
<h1>WebSocket Demo</h1>
<p>Status: {connected ? 'Connected' : 'Disconnected'}</p>
</div>
);
}

Events and Messaging

Emit and listen:

// Server
io.on('connection', (socket) => {
// Listen for custom event
socket.on('chat-message', (message) => {
console.log('Message received:', message);

// Send back to sender
socket.emit('message-received', { id: Date.now() });

// Broadcast to all clients (including sender)
io.emit('new-message', message);

// Broadcast to all except sender
socket.broadcast.emit('new-message', message);
});
});
// Client
function ChatApp() {
const [messages, setMessages] = useState([]);

useEffect(() => {
// Listen for new messages
socket.on('new-message', (message) => {
setMessages(prev => [...prev, message]);
});

return () => socket.off('new-message');
}, []);

const sendMessage = (text) => {
// Emit message to server
socket.emit('chat-message', {
text,
timestamp: Date.now()
});
};

return (
<div>
<MessageList messages={messages} />
<MessageInput onSend={sendMessage} />
</div>
);
}

Real-Time Chat Application

Complete chat implementation:

// server.js
const express = require('express');
const { createServer } = require('http');
const { Server } = require('socket.io');
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: { origin: 'http://localhost:3000' }
});
const users = new Map(); // socketId -> usernameio.on('connection', (socket) => {
console.log('User connected:', socket.id);

// Handle user login
socket.on('login', (username) => {
users.set(socket.id, username);

// Notify others
socket.broadcast.emit('user-joined', {
username,
userCount: users.size
});

// Send current users to new user
socket.emit('users-list', Array.from(users.values()));
});

// Handle chat message
socket.on('chat-message', (data) => {
const username = users.get(socket.id);

// Broadcast message with username
io.emit('new-message', {
id: Date.now(),
username,
text: data.text,
timestamp: new Date()
});
});

// Handle typing indicator
socket.on('typing', () => {
const username = users.get(socket.id);
socket.broadcast.emit('user-typing', username);
});

socket.on('stop-typing', () => {
const username = users.get(socket.id);
socket.broadcast.emit('user-stop-typing', username);
});

// Handle disconnect
socket.on('disconnect', () => {
const username = users.get(socket.id);
users.delete(socket.id);

if (username) {
io.emit('user-left', {
username,
userCount: users.size
});
}
});
});
httpServer.listen(5000);

React chat client:

// ChatApp.jsx
import { useState, useEffect, useRef } from 'react';
import io from 'socket.io-client';
const socket = io('http://localhost:5000');function ChatApp() {
const [username, setUsername] = useState('');
const [loggedIn, setLoggedIn] = useState(false);
const [messages, setMessages] = useState([]);
const [users, setUsers] = useState([]);
const [typing, setTyping] = useState([]);
const [inputValue, setInputValue] = useState('');

useEffect(() => {
// User joined
socket.on('user-joined', ({ username, userCount }) => {
setMessages(prev => [...prev, {
type: 'system',
text: `${username} joined the chat`
}]);
});

// User left
socket.on('user-left', ({ username, userCount }) => {
setMessages(prev => [...prev, {
type: 'system',
text: `${username} left the chat`
}]);
});

// New message
socket.on('new-message', (message) => {
setMessages(prev => [...prev, message]);
});

// Users list
socket.on('users-list', (usersList) => {
setUsers(usersList);
});

// Typing indicator
socket.on('user-typing', (username) => {
setTyping(prev => [...new Set([...prev, username])]);
});

socket.on('user-stop-typing', (username) => {
setTyping(prev => prev.filter(u => u !== username));
});

return () => {
socket.off('user-joined');
socket.off('user-left');
socket.off('new-message');
socket.off('users-list');
socket.off('user-typing');
socket.off('user-stop-typing');
};
}, []);

const handleLogin = (e) => {
e.preventDefault();
if (username.trim()) {
socket.emit('login', username);
setLoggedIn(true);
}
};

const handleSendMessage = (e) => {
e.preventDefault();
if (inputValue.trim()) {
socket.emit('chat-message', { text: inputValue });
setInputValue('');
socket.emit('stop-typing');
}
};

const handleTyping = (e) => {
setInputValue(e.target.value);

if (e.target.value) {
socket.emit('typing');
} else {
socket.emit('stop-typing');
}
};

if (!loggedIn) {
return (
<div className="login">
<h1>Join Chat</h1>
<form onSubmit={handleLogin}>
<input
type="text"
placeholder="Enter username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<button type="submit">Join</button>
</form>
</div>
);
}

return (
<div className="chat">
<div className="sidebar">
<h3>Users ({users.length})</h3>
<ul>
{users.map((user, i) => (
<li key={i}>{user}</li>
))}
</ul>
</div>

<div className="main">
<div className="messages">
{messages.map((msg, i) => (
<div key={i} className={msg.type === 'system' ? 'system' : 'message'}>
{msg.type === 'system' ? (
<em>{msg.text}</em>
) : (
<>
<strong>{msg.username}:</strong> {msg.text}
</>
)}
</div>
))}

{typing.length > 0 && (
<div className="typing">
{typing.join(', ')} {typing.length === 1 ? 'is' : 'are'} typing...
</div>
)}
</div>

<form onSubmit={handleSendMessage}>
<input
type="text"
placeholder="Type a message..."
value={inputValue}
onChange={handleTyping}
/>
<button type="submit">Send</button>
</form>
</div>
</div>
);
}

Rooms

Get Yakhilesh’s stories in your inbox

Join Medium for free to get updates from this writer.

Group users into rooms:

// Srver
io.on('connection', (socket) => {
// Join room
socket.on('join-room', (roomId) => {
socket.join(roomId);
console.log(`${socket.id} joined room ${roomId}`);

// Notify room
socket.to(roomId).emit('user-joined-room', socket.id);
});

// Leave room
socket.on('leave-room', (roomId) => {
socket.leave(roomId);
socket.to(roomId).emit('user-left-room', socket.id);
});

// Send to specific room
socket.on('room-message', ({ roomId, message }) => {
io.to(roomId).emit('new-room-message', {
roomId,
message,
from: socket.id
});
});

// Get room members
socket.on('get-room-members', async (roomId) => {
const sockets = await io.in(roomId).fetchSockets();
socket.emit('room-members', sockets.map(s => s.id));
});
});
// Client
function RoomChat({ roomId }) {
useEffect(() => {
// Join room on mount
socket.emit('join-room', roomId);

socket.on('new-room-message', (data) => {
if (data.roomId === roomId) {
// Handle message
}
});

// Leave room on unmount
return () => {
socket.emit('leave-room', roomId);
socket.off('new-room-message');
};
}, [roomId]);

const sendMessage = (message) => {
socket.emit('room-message', { roomId, message });
};

return <div>Room Chat</div>;
}

Private Messaging

Send to specific user:

// Server
const userSockets = new Map(); // userId -> socketId
io.on('connection', (socket) => {
socket.on('register', (userId) => {
userSockets.set(userId, socket.id);
});

socket.on('private-message', ({ to, message }) => {
const recipientSocketId = userSockets.get(to);

if (recipientSocketId) {
// Send to specific socket
io.to(recipientSocketId).emit('private-message', {
from: socket.userId,
message
});
}
});

socket.on('disconnect', () => {
// Remove from map
for (const [userId, socketId] of userSockets.entries()) {
if (socketId === socket.id) {
userSockets.delete(userId);
break;
}
}
});
});

Authentication

Authenticate WebSocket connections:

// Server
const jwt = require('jsonwebtoken');
io.use((socket, next) => {
const token = socket.handshake.auth.token;

if (!token) {
return next(new Error('Authentication error'));
}

try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
socket.userId = decoded.id;
next();
} catch (err) {
next(new Error('Invalid token'));
}
});
io.on('connection', (socket) => {
console.log('Authenticated user:', socket.userId);
});
// Client
const socket = io('http://localhost:5000', {
auth: {
token: localStorage.getItem('token')
}
});
socket.on('connect_error', (err) => {
console.log('Connection failed:', err.message);
// Redirect to login
});

Namespaces

Separate concerns:

// Server
const chatNamespace = io.of('/chat');
const notificationsNamespace = io.of('/notifications');
chatNamespace.on('connection', (socket) => {
console.log('Chat connection');
socket.on('message', (data) => {
chatNamespace.emit('new-message', data);
});
});
notificationsNamespace.on('connection', (socket) => {
console.log('Notifications connection');
socket.on('subscribe', (userId) => {
socket.join(`user-${userId}`);
});
});
// Client
const chatSocket = io('http://localhost:5000/chat');
const notifSocket = io('http://localhost:5000/notifications');
chatSocket.on('new-message', handleMessage);
notifSocket.on('notification', handleNotification);

Error Handling and Reconnection

Handle connection issues:

// Client
const socket = io('http://localhost:5000', {
reconnection: true,
reconnectionDelay: 1000,
reconnectionAttempts: 5
});
socket.on('connect', () => {
console.log('Connected');
setStatus('connected');
});
socket.on('disconnect', (reason) => {
console.log('Disconnected:', reason);
setStatus('disconnected');

if (reason === 'io server disconnect') {
// Server disconnected, manually reconnect
socket.connect();
}
});
socket.on('reconnect', (attemptNumber) => {
console.log('Reconnected after', attemptNumber, 'attempts');
});
socket.on('reconnect_attempt', (attemptNumber) => {
console.log('Reconnection attempt', attemptNumber);
setStatus('reconnecting');
});
socket.on('reconnect_error', (error) => {
console.log('Reconnection error:', error);
});
socket.on('reconnect_failed', () => {
console.log('Reconnection failed');
setStatus('failed');
});

Common Patterns

Pattern 1: Online Presence

// Server
const onlineUsers = new Set();
io.on('connection', (socket) => {
onlineUsers.add(socket.userId);
io.emit('online-users', Array.from(onlineUsers));

socket.on('disconnect', () => {
onlineUsers.delete(socket.userId);
io.emit('online-users', Array.from(onlineUsers));
});
});

Pattern 2: Typing Indicator

// Server
socket.on('typing', ({ roomId }) => {
socket.to(roomId).emit('user-typing', {
userId: socket.userId,
roomId
});
});
// Client - debounced
const debouncedStopTyping = debounce(() => {
socket.emit('stop-typing', { roomId });
}, 1000);
const handleTyping = () => {
socket.emit('typing', { roomId });
debouncedStopTyping();
};

Pattern 3: Message Acknowledgement

// Server
socket.on('message', (data, callback) => {
// Save message
const message = saveMessage(data);

// Acknowledge
callback({ success: true, messageId: message.id });

// Broadcast
socket.broadcast.emit('new-message', message);
});
// Client
socket.emit('message', { text: 'Hello' }, (response) => {
if (response.success) {
console.log('Message sent:', response.messageId);
}
});

Summary: Your WebSockets Checklist

Server Setup:

  • Install socket.io
  • Create HTTP server
  • Initialize Socket.io
  • Handle CORS

Connection Handling:

  • Listen for ‘connection’ event
  • Store socket references
  • Handle ‘disconnect’ event
  • Clean up on disconnect

Events:

  • socket.emit() send to client
  • io.emit() broadcast to all
  • socket.broadcast.emit() send to others
  • socket.to(room).emit() send to room

Rooms:

  • socket.join(roomId) join room
  • socket.leave(roomId) leave room
  • io.to(roomId).emit() message room

Authentication:

  • Middleware for auth
  • Verify JWT tokens
  • Store user info on socket
  • Handle auth errors

Practice Challenges

Build real-time features:

Challenge 1: Chat Application Multi-room chat with typing indicators.

Challenge 2: Live Notifications Real-time notification system.

Challenge 3: Collaborative Whiteboard Multiple users drawing together.

Challenge 4: Live Dashboard Real-time data updates.

Challenge 5: Multiplayer Game Simple real-time game.

Related Posts:

  • Post 38: Express.js Fundamentals
  • Post 48: JWT Authentication
  • Post 11: React Fundamentals

You are at post 51 of the 75-post series. WebSockets are now in your toolkit.

--

--

Yakhilesh
Yakhilesh

Written by Yakhilesh

I write about work, learning, and the things we overcomplicate. Clear thinking, honest notes from real experience. No hype. akhilesh.pages.dev