WritingWebSocket Fundamentals: A Practical Intro for Developers Building Real-Time Apps — LinkIT
5 min readwebsockets, beginners, real-time, javascript, fundamentals

WebSocket Fundamentals: A Practical Intro for Developers Building Real-Time Apps

A clear beginner's guide to WebSocket fundamentals—how the upgrade handshake works, when to use it, and how to send your first message in plain JavaScript.

Real-time notifications, live prices, and chat messages may need updates initiated by the server. WebSockets provide a persistent channel for two-way communication; compare that requirement with simpler one-way alternatives before choosing the protocol.

What a WebSocket Is

A WebSocket is a persistent, full-duplex communication channel between a client and a server. "Full-duplex" means both sides can send messages independently and simultaneously, like a phone call rather than a walkie-talkie.

It starts as a standard HTTP request. The client sends an Upgrade: websocket header. If the server accepts, both sides switch to the WebSocket protocol over the same TCP connection. From that point on, the connection stays open until either side closes it, and either side can send a message at any time.

This is fundamentally different from standard HTTP, where:

  • The client must always initiate
  • The server sends exactly one response per request
  • The connection closes after the response

With WebSockets, the server can push data whenever it wants, without any polling from the client.

The WebSocket Handshake

The upgrade happens through a standard HTTP GET request with specific headers:

GET /realtime HTTP/1.1
Host: api.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

The server responds with a 101 Switching Protocols status and a computed Sec-WebSocket-Accept header. After this exchange, the TCP connection is handed off to the WebSocket protocol. Standard HTTP proxies and firewalls generally handle this upgrade correctly on modern infrastructure, though some corporate environments require explicit allowlisting.

Sending Your First WebSocket Message in the Browser

The browser's built-in WebSocket API is straightforward:

const socket = new WebSocket('wss://api.example.com/realtime');
 
socket.addEventListener('open', () => {
  console.log('Connected');
  socket.send(JSON.stringify({ type: 'ping' }));
});
 
socket.addEventListener('message', (event) => {
  const data = JSON.parse(event.data);
  console.log('Received:', data);
});
 
socket.addEventListener('close', (event) => {
  console.log('Connection closed:', event.code, event.reason);
});
 
socket.addEventListener('error', (error) => {
  console.error('WebSocket error:', error);
});

Use wss:// (WebSocket Secure, over TLS) in production, never plain ws://. The error event rarely carries useful detail on its own — the subsequent close event with its code and reason is more informative.

A Minimal Node.js WebSocket Server

Using the ws library, the server side is equally direct:

const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ port: 8080 });
 
wss.on('connection', (socket, request) => {
  console.log('Client connected');
 
  socket.on('message', (raw) => {
    const msg = JSON.parse(raw);
    console.log('Received:', msg);
    socket.send(JSON.stringify({ type: 'pong' }));
  });
 
  socket.on('close', () => {
    console.log('Client disconnected');
  });
});

The ws package provides WebSocket support for Node.js. Socket.IO is a separate protocol and library with features such as rooms and reconnection; it can use WebSocket as a transport, but its clients are not interchangeable with plain WebSocket clients.

WebSocket Message Formats

WebSocket frames can carry text or binary data. Most application-level protocols use text frames with JSON payloads because JSON is easy to debug and universally supported. A common pattern is an envelope with a type field and a payload:

{
  "type": "cursor_moved",
  "payload": {
    "userId": "u_123",
    "x": 412,
    "y": 207
  }
}

For high-throughput systems where payload size matters — game state, financial ticks — binary formats like MessagePack or Protocol Buffers over binary frames can cut bandwidth meaningfully. Start with JSON unless you have a measured reason to optimize.

Close Codes and What They Mean

When a WebSocket closes, it carries a numeric code. The most important ones:

CodeMeaning
1000Normal closure
1001Going away (server shutting down)
1006Abnormal closure (no close frame received)
1011Internal server error
1013Server at capacity
4001Application-level: unauthorized (custom)

Codes in the 4000-4999 range are reserved for application-defined use. Use them to communicate application-specific close reasons to the client.

What WebSockets Do Not Handle Automatically

Understanding the gaps is as important as the basics:

  • Reconnection — the browser does not reconnect automatically on close; you must implement this yourself
  • Heartbeats — dead connections are not surfaced immediately; you need ping/pong to detect them
  • Message ordering guarantees — WebSockets are ordered within a connection, but if the client reconnects, it has no built-in mechanism to catch up on missed messages
  • Authentication — the WebSocket handshake can carry cookies and headers, but most production systems implement an explicit auth frame after connection

These are the features you will need to build, or use a library that provides them.

When to Use WebSockets (and When Not To)

Use WebSockets for:

  • Chat and messaging
  • Live document collaboration
  • Real-time dashboards where users also send commands
  • Multiplayer features

Consider SSE or long polling instead for:

  • Notification feeds (server-to-client only)
  • Live data displays with no client-to-server interaction
  • Deployments behind infrastructure that does not reliably support WebSocket upgrades

Test disconnection, reconnection, authentication expiry, and slow consumers alongside normal message delivery.

Discuss real-time application architecture with LinkIT.