๐Ÿ‘จโ€๐Ÿซ Shadab Perwez ยท Chat Protocols Workshop
WebSockets ยท GraphQL ยท JSON-RPC โ€” live comparison

๐Ÿ’ฌ Real-Time Chat โ€” 3 Protocols Side by Side

One Python server. Three different ways clients can talk to it. Below you'll find flow diagrams, "why we chose this stack" notes, and interactive demos that light up the exact server code as it runs.

๐Ÿ—๏ธ Overall Architecture

Every client (browser, mobile app, CLI) can pick the protocol that best fits the task. All three protocols share the same in-memory data (MESSAGES, ROOMS, USER_STATS).

๐Ÿ–ฅ๏ธ Browser Client client.html + JS fetch() + WebSocket() WebSocket (persistent) GraphQL POST /graphql JSON-RPC POST /rpc ๐Ÿ› ๏ธ FastAPI Server (main.py) WebSocket endpoint /ws/{room}/{user} ConnectionManager GraphQL router Strawberry schema Query.messages / rooms JSON-RPC dispatcher @method functions create_room / delete / stats ๐Ÿ’พ Shared in-memory state MESSAGES : list โ€ข ROOMS : set โ€ข USER_STATS : dict (swap for PostgreSQL / Redis in production)
๐Ÿ’ก Key insight: the protocol is just a "delivery vehicle." All three end up calling normal Python functions that read/write the same lists. Choosing a protocol is really about who initiates, how often, and what shape the data needs to be in.

๐Ÿค” Why Python + FastAPI? What Else Could We Use?

๐Ÿ Why we chose Python + FastAPI

  • Async built-in: async/await handles thousands of open WebSockets on one process.
  • One framework, three protocols: HTTP, WS and GraphQL all mount on the same FastAPI app.
  • Type hints โ†’ auto validation + docs (Pydantic, Strawberry, Swagger UI at /docs).
  • Huge learning ecosystem โ€” perfect for teaching without drowning students in boilerplate.

๐ŸŸข Node.js (Express + Socket.IO + Apollo)

  • Pro: JavaScript on both client & server โ€” one language everywhere.
  • Pro: Socket.IO auto-handles reconnection & fallbacks.
  • Con: Callback / promise nesting can get messy for beginners compared with Python's await.

โ˜• Java (Spring Boot + STOMP + GraphQL Java)

  • Pro: Enterprise-grade, strongly typed, mature tooling.
  • Pro: Great for very large teams and regulated environments.
  • Con: Verbose โ€” 3ร— the code to demo the same thing in a workshop.

๐Ÿฆ€ Go (Gorilla WebSocket + gqlgen)

  • Pro: Compiled binary, tiny memory footprint, blazing fast.
  • Pro: Goroutines are excellent for many concurrent sockets.
  • Con: Smaller GraphQL & RPC ecosystem, more manual wiring.

๐ŸŸช C# (.NET SignalR + Hot Chocolate)

  • Pro: SignalR is arguably the smoothest real-time framework anywhere.
  • Pro: First-class GraphQL via Hot Chocolate.
  • Con: Windows-centric mental model still trips up newcomers.

๐Ÿฆ„ Elixir (Phoenix Channels + Absinthe)

  • Pro: Built on Erlang VM โ€” literally designed for millions of persistent connections.
  • Pro: Fault-tolerant, hot-reloadable.
  • Con: Functional syntax has a steeper learning curve for students new to programming.
ProtocolDirectionBest for Payload shapeEndpoint here
WebSocketBoth ways ยท persistent Chat, live scores, notifications Any (usually JSON) ws://โ€ฆ/ws/{room}/{user}
GraphQLClient โ†’ Server (single request) Complex reads where UI picks fields Query language string POST /graphql
JSON-RPCClient โ†’ Server (single request) Named actions with typed params {method, params, id} POST /rpc

๐Ÿ”€ Message-Flow Diagrams

Below is a swim-lane view of what actually happens for each protocol.

1๏ธโƒฃ WebSocket โ€” persistent tunnel

Client (Alice) Network Server โ‘  HTTP Upgrade handshake โ‘ก 101 Switching Protocols โœ“ tunnel open โ‘ข send("hello") โ‘ฃ broadcast to Alice โ‘ฃ broadcast to Bob (same room!) โ€ฆ tunnel stays open, either side can push more frames โ€ฆ

2๏ธโƒฃ GraphQL โ€” one HTTP POST, flexible response

Client Server POST /graphql ยท body = { messages(room:"general") { text user } } parse + validate run resolvers { "data": { "messages": [ {text, user}, โ€ฆ ] } }

3๏ธโƒฃ JSON-RPC โ€” call a named function

Client Server { "jsonrpc":"2.0", "method":"create_room", "params":{"name":"sports"}, "id":1 } dispatcher โ†’ create_room() { "jsonrpc":"2.0", "result":{"created":"sports"}, "id":1 }
step running step succeeded step failed step not started

WebSocket โ€” persistent 2-way pipe

Unlike HTTP, a WebSocket stays open. After the handshake, either side can push data at any time โ€” perfect for chat.

Room: User:

๐Ÿ–ฅ๏ธ Client (browser)

Step 1 ยท Open handshake
new WebSocket("ws://.../ws/general/alice")
Step 2 ยท Send text frame
ws.send("hello")
Step 6 ยท Receive frame
ws.onmessage โ†’ append to chat

๐ŸŒ Persistent tunnel

๐Ÿ’ฌ
tunnel closed โ€” click Connect

๐Ÿ› ๏ธ Server (FastAPI)

Step 3 ยท Accept + register
manager.connect(room, ws)
Step 4 ยท Build message dict
{id, room, user, text, ts}
Step 5 ยท Broadcast to everyone
manager.broadcast(room, msg)
๐Ÿ“„ Server code that runs:
@app.websocket("/ws/{room}/{user}") async def websocket_endpoint(ws, room, user): await manager.connect(room, ws) # accept + track ws.username = user await manager.broadcast(room, {"type":"system", ...}) while True: text = await ws.receive_text() # waits for client msg = {"id": uuid4(), "user": user, "text": text, ...} MESSAGES.append(msg) await manager.broadcast(room, {"type":"message", **msg})

๐Ÿง  Line-by-line walkthrough

  1. @app.websocket(...) โ€” tells FastAPI "this URL uses the WebSocket protocol, not HTTP GET."
  2. manager.connect() โ€” completes the WS handshake and stores the socket in a dict keyed by room name.
  3. await ws.receive_text() โ€” suspends this coroutine until the client sends a frame. Python doesn't block โ€” thousands of other sockets can be waiting at the same time.
  4. MESSAGES.append(msg) โ€” persists to the shared "database."
  5. manager.broadcast() โ€” pushes the same JSON to every socket in the room, including other tabs.

๐Ÿ’ก Why async? Regular Python sockets would block one thread per user. asyncio lets us handle 10 000+ open WebSockets on a single process.

๐Ÿ’ฌ Chat window (open 2 tabs to see live broadcasting):

๐Ÿ“ก Live network log (all 3 protocols)