Building Real-time Applications with WebSockets
AI-Generated Content Notice
Some code examples and technical explanations in this article were generated with AI assistance. The content has been reviewed for accuracy, but please test any code snippets in your development environment before using them.
Building Real-time Applications with WebSockets: A Comprehensive Guide
Real-time applications are becoming increasingly important in modern web development. WebSockets provide a powerful way to implement real-time features. This guide will show you how to build various types of real-time applications.
Understanding WebSockets
WebSockets provide full-duplex communication channels over a single TCP connection:
// Browser-side WebSocket
const ws = new WebSocket('ws://localhost:8080')
ws.onopen = () => {
console.log('Connected to WebSocket server')
}
ws.onmessage = (event) => {
console.log('Received:', event.data)
}
ws.onclose = () => {
console.log('Disconnected from WebSocket server')
}
ws.onerror = (error) => {
console.error('WebSocket error:', error)
}
Setting Up a WebSocket Server
Let's create a WebSocket server using Node.js and ws library:
// server.js
const WebSocket = require('ws')
const server = new WebSocket.Server({ port: 8080 })
server.on('connection', (ws) => {
console.log('New client connected')
ws.on('message', (message) => {
console.log('Received:', message)
// Broadcast to all clients
server.clients.forEach((client) => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(message)
}
})
})
ws.on('close', () => {
console.log('Client disconnected')
})
})
Project: Real-time Chat Application
Let's build a complete chat application with rooms and private messaging:
// types.ts
interface ChatMessage {
id: string
type: 'message' | 'system'
room: string
sender: string
content: string
timestamp: number
}
interface ChatRoom {
id: string
name: string
users: Set<string>
}
// server/ChatServer.ts
import { WebSocket, WebSocketServer } from 'ws'
import { v4 as uuidv4 } from 'uuid'
class ChatServer {
private wss: WebSocketServer
private rooms: Map<string, ChatRoom>
private clients: Map<WebSocket, string> // WebSocket -> userId
constructor(port: number) {
this.wss = new WebSocketServer({ port })
this.rooms = new Map()
this.clients = new Map()
// Create default room
this.rooms.set('general', {
id: 'general',
name: 'General',
users: new Set()
})
this.setupWebSocketServer()
}
private setupWebSocketServer() {
this.wss.on('connection', (ws: WebSocket) => {
const userId = uuidv4()
this.clients.set(ws, userId)
// Join default room
this.joinRoom(ws, 'general')
ws.on('message', (data: string) => {
const message = JSON.parse(data)
this.handleMessage(ws, message)
})
ws.on('close', () => {
this.handleDisconnect(ws)
})
})
}
private handleMessage(ws: WebSocket, message: any) {
const userId = this.clients.get(ws)
switch (message.type) {
case 'chat':
this.broadcastToRoom(message.room, {
id: uuidv4(),
type: 'message',
room: message.room,
sender: userId,
content: message.content,
timestamp: Date.now()
})
break
case 'join_room':
this.joinRoom(ws, message.room)
break
case 'leave_room':
this.leaveRoom(ws, message.room)
break
case 'private_message':
this.sendPrivateMessage(
userId!,
message.recipient,
message.content
)
break
}
}
private broadcastToRoom(roomId: string, message: ChatMessage) {
const room = this.rooms.get(roomId)
if (!room) return
this.wss.clients.forEach((client) => {
if (
client.readyState === WebSocket.OPEN &&
room.users.has(this.clients.get(client)!)
) {
client.send(JSON.stringify(message))
}
})
}
private joinRoom(ws: WebSocket, roomId: string) {
const userId = this.clients.get(ws)
const room = this.rooms.get(roomId)
if (!room || !userId) return
room.users.add(userId)
// Notify room about new user
this.broadcastToRoom(roomId, {
id: uuidv4(),
type: 'system',
room: roomId,
sender: 'system',
content: `User ${userId} joined the room`,
timestamp: Date.now()
})
}
private leaveRoom(ws: WebSocket, roomId: string) {
const userId = this.clients.get(ws)
const room = this.rooms.get(roomId)
if (!room || !userId) return
room.users.delete(userId)
// Notify room about user leaving
this.broadcastToRoom(roomId, {
id: uuidv4(),
type: 'system',
room: roomId,
sender: 'system',
content: `User ${userId} left the room`,
timestamp: Date.now()
})
}
private sendPrivateMessage(
senderId: string,
recipientId: string,
content: string
) {
const message = {
id: uuidv4(),
type: 'private_message',
sender: senderId,
content,
timestamp: Date.now()
}
this.wss.clients.forEach((client) => {
const clientId = this.clients.get(client)
if (
client.readyState === WebSocket.OPEN &&
clientId === recipientId
) {
client.send(JSON.stringify(message))
}
})
}
private handleDisconnect(ws: WebSocket) {
const userId = this.clients.get(ws)
if (!userId) return
// Remove user from all rooms
this.rooms.forEach((room) => {
if (room.users.has(userId)) {
room.users.delete(userId)
this.broadcastToRoom(room.id, {
id: uuidv4(),
type: 'system',
room: room.id,
sender: 'system',
content: `User ${userId} disconnected`,
timestamp: Date.now()
})
}
})
this.clients.delete(ws)
}
}
// client/ChatClient.ts
class ChatClient {
private ws: WebSocket
private messageHandlers: Map<string, (message: any) => void>
constructor(url: string) {
this.ws = new WebSocket(url)
this.messageHandlers = new Map()
this.ws.onmessage = (event) => {
const message = JSON.parse(event.data)
this.handleMessage(message)
}
}
public sendMessage(room: string, content: string) {
this.ws.send(JSON.stringify({
type: 'chat',
room,
content
}))
}
public joinRoom(room: string) {
this.ws.send(JSON.stringify({
type: 'join_room',
room
}))
}
public leaveRoom(room: string) {
this.ws.send(JSON.stringify({
type: 'leave_room',
room
}))
}
public sendPrivateMessage(recipient: string, content: string) {
this.ws.send(JSON.stringify({
type: 'private_message',
recipient,
content
}))
}
public onMessage(type: string, handler: (message: any) => void) {
this.messageHandlers.set(type, handler)
}
private handleMessage(message: any) {
const handler = this.messageHandlers.get(message.type)
if (handler) {
handler(message)
}
}
}
// React component example
import React, { useEffect, useState } from 'react'
function ChatRoom({ roomId }: { roomId: string }) {
const [messages, setMessages] = useState<ChatMessage[]>([])
const [input, setInput] = useState('')
const [client, setClient] = useState<ChatClient | null>(null)
useEffect(() => {
const chatClient = new ChatClient('ws://localhost:8080')
setClient(chatClient)
chatClient.onMessage('message', (message) => {
setMessages((prev) => [...prev, message])
})
chatClient.onMessage('system', (message) => {
setMessages((prev) => [...prev, message])
})
chatClient.joinRoom(roomId)
return () => {
chatClient.leaveRoom(roomId)
}
}, [roomId])
const sendMessage = () => {
if (input.trim() && client) {
client.sendMessage(roomId, input)
setInput('')
}
}
return (
<div className="chat-room">
<div className="messages">
{messages.map((message) => (
<div
key={message.id}
className={`message ${message.type}`}
>
<span className="sender">{message.sender}</span>
<span className="content">{message.content}</span>
<span className="time">
{new Date(message.timestamp).toLocaleTimeString()}
</span>
</div>
))}
</div>
<div className="input-area">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
/>
<button onClick={sendMessage}>Send</button>
</div>
</div>
)
}
Real-time Dashboard Example
Let's create a real-time dashboard that updates automatically:
// server/DashboardServer.ts
class DashboardServer {
private wss: WebSocketServer
private metrics: Map<string, number>
private updateInterval: NodeJS.Timer
constructor(port: number) {
this.wss = new WebSocketServer({ port })
this.metrics = new Map()
this.setupWebSocketServer()
this.startMetricsUpdate()
}
private setupWebSocketServer() {
this.wss.on('connection', (ws: WebSocket) => {
// Send initial metrics
ws.send(JSON.stringify({
type: 'metrics',
data: Object.fromEntries(this.metrics)
}))
})
}
private startMetricsUpdate() {
this.updateInterval = setInterval(() => {
// Update metrics
this.metrics.set('cpu', Math.random() * 100)
this.metrics.set('memory', Math.random() * 16384)
this.metrics.set('requests', Math.floor(Math.random() * 1000))
// Broadcast to all clients
this.broadcastMetrics()
}, 1000)
}
private broadcastMetrics() {
const message = JSON.stringify({
type: 'metrics',
data: Object.fromEntries(this.metrics)
})
this.wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(message)
}
})
}
}
// React dashboard component
function Dashboard() {
const [metrics, setMetrics] = useState({
cpu: 0,
memory: 0,
requests: 0
})
useEffect(() => {
const ws = new WebSocket('ws://localhost:8080')
ws.onmessage = (event) => {
const message = JSON.parse(event.data)
if (message.type === 'metrics') {
setMetrics(message.data)
}
}
return () => ws.close()
}, [])
return (
<div className="dashboard">
<div className="metric">
<h3>CPU Usage</h3>
<div className="value">{metrics.cpu.toFixed(1)}%</div>
</div>
<div className="metric">
<h3>Memory Usage</h3>
<div className="value">
{(metrics.memory / 1024).toFixed(2)} GB
</div>
</div>
<div className="metric">
<h3>Requests/sec</h3>
<div className="value">{metrics.requests}</div>
</div>
</div>
)
}
Best Practices
-
Connection Management
- Implement reconnection logic
- Handle connection errors
- Clean up resources properly
- Monitor connection health
-
Performance
- Minimize message size
- Batch updates when possible
- Use binary protocols for large data
- Implement rate limiting
-
Security
- Use WSS (WebSocket Secure)
- Validate messages
- Implement authentication
- Prevent DoS attacks
-
Scalability
- Use Redis for pub/sub
- Implement horizontal scaling
- Monitor performance
- Handle backpressure
Common WebSocket Use Cases
- Real-time Collaboration
interface CursorPosition {
userId: string
x: number
y: number
}
// Broadcast cursor positions
ws.send(JSON.stringify({
type: 'cursor_move',
position: { x, y }
}))
- Live Updates
// Subscribe to updates
ws.send(JSON.stringify({
type: 'subscribe',
topics: ['prices', 'inventory']
}))
- Multiplayer Games
interface GameState {
players: Map<string, PlayerState>
gameObjects: GameObject[]
}
// Send player action
ws.send(JSON.stringify({
type: 'player_action',
action: 'move',
direction: { x: 1, y: 0 }
}))
Conclusion
WebSockets enable powerful real-time features in web applications:
- Bi-directional communication
- Low latency updates
- Efficient resource usage
- Scalable architecture
Keep exploring different use cases and implementing best practices for robust real-time applications.