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
| Feature | HTTP | WebSocket |
|---|---|---|
| Connection | Request-Response | Persistent |
| Direction | Client → Server | Bidirectional |
| Overhead | High (headers) | Low |
| Use Case | REST APIs | Real-time apps |
Setting Up a WebSocket Server
Using Socket.io with Node.js
javascriptconst 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
javascriptimport { 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
javascriptio.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
javascriptconst { 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
nginxupstream 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
- Heartbeat/Ping-Pong: Keep connections alive
- Reconnection Logic: Handle disconnections gracefully
- Message Queuing: Don't lose messages during disconnection
- Rate Limiting: Prevent abuse
- Compression: Enable for large messages
- 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