Episode 3 — NodeJS MongoDB Backend Architecture / 3.15 — Realtime Communication WebSockets

3.15.a — Understanding WebSockets

WebSocket is a communication protocol providing full-duplex, persistent connections over a single TCP connection, enabling real-time data exchange between client and server without repeated HTTP requests.


<< README | Next: 3.15.b — HTTP Polling & Alternatives >>


1. What is the WebSocket Protocol?

WebSocket is a communication protocol defined in RFC 6455 that provides a persistent, full-duplex communication channel over a single TCP connection. Unlike HTTP, where the client always initiates communication, WebSocket allows both client and server to send messages independently at any time.

Traditional HTTP:
  Client ──Request──>  Server
  Client <──Response── Server
  (Connection closes)

WebSocket:
  Client <══════════════> Server
  (Persistent bidirectional connection)
  Either side can send data at any time

Think of HTTP as a walkie-talkie (one speaks, the other listens, then responds) and WebSocket as a phone call (both parties can speak and listen simultaneously).


2. HTTP vs WebSocket: The Fundamental Difference

FeatureHTTPWebSocket
CommunicationRequest-Response (half-duplex)Full-duplex (bidirectional)
ConnectionNew connection per request (HTTP/1.1 uses keep-alive)Single persistent connection
Who initiates?Client always initiatesEither side can send anytime
OverheadHeaders sent with every request (~800 bytes+)Minimal frame overhead (~2-6 bytes)
Protocolhttp:// / https://ws:// / wss://
StateStateless by defaultStateful — connection stays open
Use caseCRUD operations, page loads, APIsReal-time updates, chat, gaming
Server PushNot native (workarounds exist)Native — server pushes freely
HTTP Timeline:
  t=0s   Client: GET /messages → Server responds with messages
  t=5s   Client: GET /messages → Server responds (same or new)
  t=10s  Client: GET /messages → Server responds (same or new)
  ↑ Client must keep asking. Wasteful if nothing changed.

WebSocket Timeline:
  t=0s   Client connects → Connection established
  t=3s   Server: "New message from Alice" → Client receives instantly
  t=7s   Client: "Reply to Alice" → Server receives instantly
  t=12s  Server: "Alice is typing..." → Client receives instantly
  ↑ Data flows freely in both directions. No wasted requests.

3. The WebSocket Handshake: HTTP Upgrade Mechanism

WebSocket connections begin with a standard HTTP request that gets upgraded to a WebSocket connection. This is called the WebSocket handshake.

Step-by-Step Handshake

Step 1: Client sends HTTP GET with Upgrade headers
─────────────────────────────────────────────────
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: http://example.com

Step 2: Server responds with 101 Switching Protocols
─────────────────────────────────────────────────────
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Step 3: Connection is now WebSocket (no more HTTP)
──────────────────────────────────────────────────
Both sides can freely send WebSocket frames.

Key headers explained:

HeaderPurpose
Upgrade: websocketRequests protocol switch to WebSocket
Connection: UpgradeSignals the connection should be upgraded
Sec-WebSocket-KeyRandom base64-encoded value for security handshake
Sec-WebSocket-AcceptServer's proof it received the key (SHA-1 hash)
Sec-WebSocket-VersionProtocol version (13 is current standard)

Because the handshake starts as HTTP, WebSocket connections work through standard web infrastructure (proxies, load balancers, firewalls) that understand HTTP.


4. WebSocket Protocols: ws:// and wss://

ws://example.com/socket    → Unencrypted WebSocket (like HTTP)
wss://example.com/socket   → Encrypted WebSocket (like HTTPS, uses TLS)
ProtocolEncryptionDefault PortUse Case
ws://None80Local development only
wss://TLS/SSL443Production — ALWAYS use this

Always use wss:// in production. Unencrypted WebSocket connections are vulnerable to man-in-the-middle attacks, and many corporate proxies/firewalls will block ws:// connections.

// Client connection examples
const devSocket = new WebSocket('ws://localhost:3000');
const prodSocket = new WebSocket('wss://api.myapp.com/socket');

5. WebSocket Lifecycle

Every WebSocket connection follows a predictable lifecycle with three main phases:

┌──────────┐    ┌────────────────────┐    ┌──────────┐
│  OPEN    │───>│  MESSAGE EXCHANGE  │───>│  CLOSE   │
│ (once)   │    │  (many times)      │    │  (once)  │
└──────────┘    └────────────────────┘    └──────────┘

Native Browser WebSocket API

// Creating a WebSocket connection
const socket = new WebSocket('wss://api.example.com/ws');

// 1. OPEN — Connection established
socket.addEventListener('open', (event) => {
  console.log('Connected to server!');
  socket.send('Hello, Server!');
});

// 2. MESSAGE — Data received from server
socket.addEventListener('message', (event) => {
  console.log('Server says:', event.data);

  // Parse JSON if server sends JSON
  const data = JSON.parse(event.data);
  console.log('Parsed:', data);
});

// 3. CLOSE — Connection closed
socket.addEventListener('close', (event) => {
  console.log('Disconnected:', event.code, event.reason);
  // event.code: 1000 = normal, 1006 = abnormal, etc.
  // event.wasClean: boolean — was it a clean close?
});

// 4. ERROR — Something went wrong
socket.addEventListener('error', (event) => {
  console.error('WebSocket error:', event);
});

// Sending data to the server
socket.send('Hello!');                          // String
socket.send(JSON.stringify({ type: 'chat', message: 'Hi' })); // JSON

// Closing the connection
socket.close(1000, 'Normal closure'); // code, reason

Close Codes Reference

CodeMeaning
1000Normal closure
1001Going away (page navigation, server shutdown)
1002Protocol error
1003Unsupported data type
1006Abnormal closure (no close frame received)
1008Policy violation
1011Server encountered unexpected condition

6. Use Cases for WebSocket

WebSocket excels in scenarios requiring instant, bidirectional communication:

Chat Applications

// Server receives message and broadcasts to all users in room
socket.on('chat-message', (data) => {
  io.to(data.room).emit('new-message', {
    user: socket.user.name,
    text: data.text,
    timestamp: Date.now()
  });
});

Live Notifications

// Server pushes notification when something happens
function notifyUser(userId, notification) {
  io.to(`user:${userId}`).emit('notification', {
    title: notification.title,
    body: notification.body,
    link: notification.link,
    createdAt: new Date()
  });
}

Collaborative Editing (Google Docs style)

// User makes a change → broadcast to all collaborators
socket.on('document-change', (delta) => {
  socket.to(`doc:${delta.documentId}`).emit('remote-change', {
    userId: socket.user.id,
    operations: delta.ops,
    version: delta.version
  });
});

Live Sports / Stock Tickers

// Server pushes price updates as they happen
function broadcastPriceUpdate(symbol, price) {
  io.to(`stock:${symbol}`).emit('price-update', {
    symbol,
    price,
    change: calculateChange(symbol, price),
    timestamp: Date.now()
  });
}

Multiplayer Gaming

// Player position updates at high frequency
socket.on('player-move', (position) => {
  socket.to(gameRoom).emit('player-moved', {
    playerId: socket.id,
    x: position.x,
    y: position.y,
    velocity: position.velocity
  });
});

7. Comparison: Polling vs Long Polling vs SSE vs WebSocket

FeatureShort PollingLong PollingSSEWebSocket
DirectionClient → ServerClient → ServerServer → ClientBidirectional
ConnectionNew per requestHeld open, then newPersistent (one-way)Persistent (two-way)
LatencyHigh (interval-based)MediumLowVery Low
Server loadHigh (many requests)MediumLowLow
ComplexitySimpleMediumSimpleMedium-High
Browser supportUniversalUniversalModern browsersModern browsers
HTTP compatibleYesYesYesStarts as HTTP, then upgrades
Binary dataVia encodingVia encodingNo (text only)Yes (native)
Auto-reconnectN/A (new requests)Must implementBuilt-inMust implement
Best forSimple dashboardsModerate updatesNews feeds, logsChat, gaming, collab

8. When NOT to Use WebSocket

WebSocket is not always the right choice:

ScenarioBetter AlternativeWhy
Simple CRUD operationsREST API (HTTP)No need for persistent connections
Infrequent updates (every 30+ seconds)Short polling or SSEWebSocket overhead not justified
One-way server updates onlySSESimpler, automatic reconnection
Static content deliveryHTTP + CDNWebSocket adds unnecessary complexity
SEO-critical contentHTTPSearch engines understand HTTP, not WS
Simple form submissionsHTTP POSTStandard HTTP is perfectly fine

Rule of thumb: If you only need to send data from server to client, consider SSE first. If communication is infrequent, consider polling. Use WebSocket when you need frequent, bidirectional, low-latency communication.


Key Takeaways

  1. WebSocket provides full-duplex communication over a single persistent TCP connection
  2. The connection starts with an HTTP Upgrade handshake (101 Switching Protocols)
  3. Always use wss:// (encrypted) in production environments
  4. The lifecycle is simple: Open -> Message (many) -> Close
  5. WebSocket excels at chat, notifications, collaborative editing, gaming, and live data
  6. It is not a replacement for HTTP -- use it only when real-time bidirectional communication is needed
  7. Overhead per message is tiny (~2-6 bytes) compared to HTTP headers (~800+ bytes)

Explain-It Challenge

Scenario: Your team is building a project management tool (like Trello). The PM asks: "Should we use WebSocket for the entire application?"

Explain which parts of the application would benefit from WebSocket (and why) and which parts should remain as standard REST API calls. Consider features like: viewing boards, creating tasks, dragging cards between columns, real-time collaboration when multiple users view the same board, and user profile settings. Justify each decision.


<< README | Next: 3.15.b — HTTP Polling & Alternatives >>