Home / Articles / Implementing Real-Time Features with WebSockets

Implementing Real-Time Features with WebSockets

Realtime
Anant

Written by: Anant

Software Engineer | Systems & Web

WebSockets enable bidirectional, real-time communication between clients and servers. This guide covers everything you need to build production-ready real-time applications.

What are WebSockets?

Unlike HTTP, which follows a request-response pattern, WebSockets maintain a persistent connection that allows both client and server to send messages at any time.

WebSocket vs HTTP

FeatureHTTPWebSocket
ConnectionRequest-ResponsePersistent
DirectionClient → ServerBidirectional
OverheadHigh (headers)Low
Use CaseREST APIsReal-time apps

Setting Up a WebSocket Server

Using Socket.io with Node.js

javascript
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"]
}
});
io.on('connection', (socket) => {
console.log('User connected:', socket.id);
socket.on('message', (data) => {
console.log('Received:', data);
// Broadcast to all clients
io.emit('message', data);
});
socket.on('disconnect', () => {
console.log('User disconnected:', socket.id);
});
});
httpServer.listen(3001, () => {
console.log('WebSocket server running on port 3001');
});

Client Implementation

React Client

javascript
function Chat() {
const [socket, setSocket] = useState(null);
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
useEffect(() => {
const newSocket = io('http://localhost:3001');
setSocket(newSocket);
newSocket.on('message', (message) => {
setMessages((prev) => [...prev, message]);
});
return () => newSocket.close();
}, []);
const sendMessage = () => {
if (socket && input.trim()) {
socket.emit('message', {
text: input,
timestamp: Date.now()
});
setInput('');
}
};
return (
<div>
<div className="messages">
{messages.map((msg, i) => (
<div key={i}>{msg.text}</div>
))}
</div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
/>
<button onClick={sendMessage}>Send</button>
</div>
);
}

Advanced Features

Rooms and Namespaces

javascript
// Join a room
socket.on('join-room', (roomId) => {
socket.join(roomId);
console.log(`User ${socket.id} joined room ${roomId}`);
});
// Send message to specific room
socket.on('room-message', ({ roomId, message }) => {
io.to(roomId).emit('message', message);
});
// Leave room
socket.on('leave-room', (roomId) => {
socket.leave(roomId);
});

Authentication

javascript
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.userId;
next();
} catch (err) {
next(new Error('Authentication error'));
}
});

Typing Indicators

javascript
// Server
socket.on('typing', ({ roomId, username }) => {
socket.to(roomId).emit('user-typing', username);
});
socket.on('stop-typing', ({ roomId, username }) => {
socket.to(roomId).emit('user-stop-typing', username);
});
// Client
const handleTyping = () => {
socket.emit('typing', { roomId, username });
clearTimeout(typingTimeout);
typingTimeout = setTimeout(() => {
socket.emit('stop-typing', { roomId, username });
}, 1000);
};

Scaling WebSockets

Using Redis Adapter

javascript
const { createAdapter } = require('@socket.io/redis-adapter');
const { createClient } = require('redis');
const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();
Promise.all([pubClient.connect(), subClient.connect()]).then(() => {
io.adapter(createAdapter(pubClient, subClient));
});

Load Balancing

nginx
upstream websocket {
ip_hash; # Sticky sessions
server backend1:3001;
server backend2:3001;
server backend3:3001;
}
server {
listen 80;
location /socket.io/ {
proxy_pass http://websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}

Best Practices

  1. Heartbeat/Ping-Pong: Keep connections alive
  2. Reconnection Logic: Handle disconnections gracefully
  3. Message Queuing: Don't lose messages during disconnection
  4. Rate Limiting: Prevent abuse
  5. Compression: Enable for large messages
  6. Security: Validate all incoming data

Error Handling

javascript
// Server
socket.on('error', (error) => {
console.error('Socket error:', error);
});
// Client
socket.on('connect_error', (error) => {
console.error('Connection error:', error);
// Implement exponential backoff
});
socket.on('disconnect', (reason) => {
if (reason === 'io server disconnect') {
// Server disconnected, reconnect manually
socket.connect();
}
});

Final Takeaway

WebSockets are essential for building modern real-time applications. With proper architecture and scaling strategies, you can build systems that handle millions of concurrent connections efficiently.

Anant

Bridging the gap between high-level applications and low-level systems. Crafting resilient software with a focus on performance and observability.

Expertise

  • Systems Engineering
  • Full Stack Development
  • Cloud Infrastructure
  • Digital Signal Processing
  • Embedded Systems

Stay Connected

Open to opportunities and interesting conversations.

Get in Touch

© 2026 Anant. All rights reserved.

Systems Operational