C.W.K.
Stream
Lesson 01 of 07 · published

Designing a Protocol

~12 min · protocol, envelope, type-field

Level 0Poller
0 XP0/60 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

The type + data envelope

WebSocket gives you a stream of opaque messages. Adding structure is your job. The dominant pattern across the industry — Slack, Discord, GraphQL-WS, every chat library — is a small JSON envelope with a type string and a data object. The type field tells you which schema the data follows. Everything else falls out from this single decision.

Namespacing types

Use dot-notation: chat.message, chat.typing, user.joined, user.status, room.join, game.move. The first segment is the domain; the second is the verb or event. Stays sortable, greppable, and natural to extend.

Errors are messages too

An error response is just another message: {type: 'error', code: 'rate_limited', message: '...', ref_id: 'xyz'}. Same envelope, different schema. Treat errors as first-class, not as a special transport-level concept.

Code

Protocol envelope examples·json
// chat message
{
  "type": "chat.message",
  "data": {
    "room": "general",
    "text": "hello",
    "ts":   "2026-05-03T10:30:00Z"
  }
}

// system event
{
  "type": "user.joined",
  "data": { "user_id": "abc123", "username": "Alice" }
}

// error
{
  "type":    "error",
  "code":    "rate_limited",
  "message": "slow down — max 20 messages/sec",
  "ref_id":  "msg-xyz-123"
}
Server-side router by type·python
async def dispatch(ws, user, msg: dict):
    handlers = {
        'chat.message': handle_chat,
        'chat.typing':  handle_typing,
        'room.join':    handle_join,
        'room.leave':   handle_leave,
        'pong':         handle_pong,
    }
    fn = handlers.get(msg.get('type'))
    if fn is None:
        await ws.send_json({
            'type': 'error',
            'code': 'unknown_type',
            'message': f"unknown type: {msg.get('type')!r}",
        })
        return
    await fn(ws, user, msg.get('data', {}))

External links

Exercise

Define the protocol for a simple chat app: list every message type with fields (chat.message, chat.typing, user.joined, user.left, room.join, room.leave, error). Write it as an AsyncAPI 3.0 document. Even if you don't generate code from it, the act of writing the spec catches missing fields.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.