Skip to content

Message Design

The foundations module taught you how to open a connection and push bytes both ways. But a connection that can carry anything tells you nothing about what those bytes mean. This module — Message Design — is where you stop sending raw text and start sending a protocol.

Here is the thing nobody tells you when you first reach for a WebSocket: the API hands you send(data) and onmessage(event), and that is all. The connection is an opaque pipe. It will happily carry the string 'hi', a stringified JSON object, or a chunk of binary — and it has no opinion about any of them. There is no built-in notion of a “chat message”, a “request”, an “error”, or a “version”.

That emptiness is a feature, not an oversight. HTTP bakes in headers, status codes, methods, and content negotiation; a WebSocket bakes in almost nothing so that you can design exactly the conversation your application needs. The flip side is responsibility: whatever structure your messages have, you put it there.

flowchart TB
  subgraph app["Your application"]
    a1["chat lines · presence · edits · requests"]
  end
  subgraph proto["Your message protocol — THIS module"]
    p1["type · payload · id · version · schema"]
  end
  subgraph ws["WebSocket transport"]
    w1["send(text or binary) · onmessage"]
  end
  app --> proto --> ws
  ws -- "opaque bytes" --> ws
The layers: transport carries bytes, your protocol carries meaning

The middle layer is the one with no default. Designing it well is the difference between a protocol you can grow for years and one that becomes a tangle of special-cased strings.

Five lessons, each adding one essential idea to your message design toolkit:

  1. Message Design (you are here) — why the socket is empty and you supply the meaning.
  2. Framing messages — WebSocket messages are already discrete, but you still need an application protocol; one JSON object per message.
  3. JSON vs binary — readable and universal JSON versus compact binary formats like MessagePack, Protobuf and CBOR, and when each wins.
  4. Request/response over WebSocket — the socket is message-oriented, not request/reply; you add a correlation id to match an answer to its question.
  5. Schemas and versioning — a message envelope, validating what arrives, and evolving the protocol without breaking older clients.

Below is the smallest possible step up from sending plain text: sending a JSON object and parsing it on the other side. It runs in your browser against an in-page echo socket that uses the real WebSocket API — same onopen, onmessage, send, close — so there is no server to start. In a real app, only the first line changes to new WebSocket('wss://your-server').

Notice that the moment we send an object instead of a bare string, the receiver can ask meaningful questions: what kind of message is this? who sent it? When?

JavaScript

The socket carried text both ways and never once cared that it was JSON. The structure — type, from, text — exists only because we agreed on it and put it there. Every lesson that follows refines that agreement.

What meaning does the WebSocket transport give to the data you send?
In the demo, why can the receiver tell it got a "chat" message?
Which best describes the role of the message-protocol layer?