Skip to content
API Design Learn/Capstone Case Studies
Browsing as a guest. Sign in to save your progress and earn XP as you complete chapters.

Design a Chat API

11 min read

You'll learn to

  • -Choose and justify a real-time protocol for a chat system, combining WebSocket-based delivery with a REST API for history and setup
  • -Design presence, delivery status, and read receipts as first-class, queryable state rather than ephemeral signals

A chat API is fundamentally a real-time problem wrapped around a fairly conventional set of resources - conversations, messages, participants - which makes it a strong test of whether a candidate picks the protocol that actually fits each part of the problem, rather than forcing one protocol to do everything.

Step 1-3: Clients, Resources, and a Deliberately Mixed Protocol Choice

Clients: a mobile and web chat client, needing both real-time message delivery and the ability to load conversation history on first open. This naturally splits into two protocol needs rather than one: a persistent, bidirectional connection for live messages (gRPC's bidirectional streaming, or a WebSocket-based approach, depending on client platform constraints), and a conventional REST API for everything that isn't inherently real-time - fetching conversation history, managing participants, searching past messages.

Real-Time Delivery

Bidirectional streaming for live message delivery
service ChatService {
  rpc Connect (stream ClientMessage) returns (stream ServerMessage);
}

message ClientMessage {
  oneof payload {
    SendMessage send = 1;
    MarkAsRead read_receipt = 2;
    TypingIndicator typing = 3;
  }
}

A single bidirectional stream (from the gRPC streaming chapter) carries every real-time event in both directions - outgoing messages, read receipts, typing indicators - using `oneof` (from the wire-evolution chapter) to represent the different kinds of client-to-server events over one connection, rather than needing a separate stream per event type.

REST for Everything Else

Conventional REST for history and setup, not real-time delivery
GET /conversations/501/messages?limit=50&before=msg_1200
GET /conversations/501/participants
POST /conversations

Loading the last 50 messages when a user opens a conversation, or listing participants, has no real-time component - it's a conventional paginated fetch (cursor-based, from the pagination chapter), and forcing this through the same real-time streaming connection would add unneeded complexity for no benefit over a plain REST call.

Presence and Delivery Status as Queryable State

Delivery and read status as explicit, queryable fields on the message resource
{
  "id": "msg_1201",
  "sender_id": "user_42",
  "text": "Hey, are you free later?",
  "sent_at": "2026-08-07T14:00:00Z",
  "delivery_status": "read",
  "read_by": [{"user_id": "user_99", "read_at": "2026-08-07T14:01:30Z"}]
}

Delivery status and read receipts shouldn't be purely ephemeral, in-the-moment signals lost the instant a client isn't connected to receive them - modeling them as durable, queryable fields on the message resource means a client that reconnects after being offline can fetch the current, correct state via the REST API, rather than having missed a live event permanently.

A strong signal on this prompt is explicitly justifying the REST-plus-streaming split rather than defaulting to streaming for everything - naming "this specific part doesn't actually need real-time delivery" is exactly the protocol-choice judgment this course's opening module set up as the actual skill being tested.

Interview Signal is part of Pro

See a real weak answer next to a real strong one for this exact topic.

Quiz is part of Pro

Test what you just read with a short quiz, and bank the XP.

Ready to Build This?

Design a Chat API in the API Design Lab's Full System Design act.

ScaleDojo Logo
Initializing ScaleDojo