contour logo

esskayesss

BLOG
tutorialSERIES: web-development
March 20, 2024 · 5 minute read

Building a Real-time Chat Application with WebSockets

Learn how to create a scalable real-time chat application using WebSocket protocol, with practical examples and performance optimization tips

Building Real-time Chat with WebSockets

In real-time communication, latency is not just a technical metric—it's the difference between a conversation and a monologue. Werner Vogels

The rise of real-time applications has transformed how we think about web communication. While HTTP excels at request-response patterns, WebSockets open up a world of persistent, bidirectional connections.

CAPTION: WebSocket vs HTTP Protocol Comparison

Let's build a basic chat server that handles thousands of concurrent connections. First, set up your WebSocket server:

1
import { WebSocketServer } from 'ws';
2
3
const wss = new WebSocketServer({ port: 8080 });
4
5
wss.on('connection', (ws) => {
6
ws.on('message', (data) => {
7
// Broadcast to all connected clients
8
wss.clients.forEach(client => {
9
client.send(data.toString());
10
});
11
});
12
});

The best code is no code at all. Every new line of code you willingly bring into the world is code that has to be debugged, read, and maintained. Jeff Atwood

On the client side, establishing a connection is equally straightforward:

1
const socket = new WebSocket('ws://localhost:8080');
2
3
socket.onmessage = (event) => {
4
console.log('Message from server:', event.data);
5
};

CAPTION: Real-time Message Flow Diagram

But the real magic happens when we start handling scale. Here's how our chat system performs under load:

Concurrent UsersMemory UsageCPU LoadLatency
1,000256MB5%50ms
10,000512MB15%75ms
100,0002GB45%120ms

Optimizing for Scale

Let's explore some optimization techniques:

  1. Message Batching: Instead of sending each message immediately, batch them in 50ms windows
  2. Protocol Compression: Implement per-message deflate compression
  3. Connection Pooling: Reuse WebSocket connections when possible

Here's how message batching looks in practice:

1
let messageQueue = [];
2
const BATCH_INTERVAL = 50; // ms
3
4
setInterval(() => {
5
if (messageQueue.length > 0) {
6
const batch = JSON.stringify(messageQueue);
7
wss.clients.forEach(client => {
8
client.send(batch);
9
});
10
messageQueue = [];
11
}
12
}, BATCH_INTERVAL);

Looking Ahead

In future posts, we'll explore:

  • Implementing reconnection strategies
  • Handling different message types
  • Building presence awareness
  • Scaling beyond a single node

Remember, as Donald Knuth reminds us:

Premature optimization is the root of all evil (or at least most of it) in programming.