Oct 20, 20183 min read

Implementing Real-Time Features with WebSockets

Realtime

Implementing Real-Time Features with WebSockets

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
import { useEffect, useState } from 'react'; import { io } from 'socket.io-client'; 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(); } });

Conclusion

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.

Written by Anant Kumar

Systems Engineer & Full Stack Developer

Anant Kumar

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 Kumar. All rights reserved.

Systems Operational