interactive explainer

How your
ChatGPT Clone
actually works

A visual walkthrough of every layer — from the button you click to the AI reply that appears.

⚛️ React 🔌 WebSockets 🐍 FastAPI 🐳 Docker
Scroll to explore
The stack

4 layers,
one app

Each layer has exactly one job. Together they build a real-time AI chat app.

⚛️
Layer 1
React Frontend
The visual layer. Renders every button, message bubble, and sidebar item. Listens to state changes and re-draws only what changed — no full-page reloads.
App.jsx Sidebar.jsx Message.jsx ChatInput.jsx useChat.js
🔌
Layer 2
WebSocket Bridge
The live pipe. Unlike normal HTTP requests, this connection stays open permanently — so the server can push words to the browser the instant Claude generates them.
useChat.js ws/{conv_id} stream_chunk auto-reconnect
🐍
Layer 3
FastAPI Backend
The brain. Receives messages, maintains conversation history, calls the Claude API, and streams every token back over the WebSocket as it arrives.
main.py GET /conversations messages.stream() Dict[str, List]
🐳
Layer 4
Docker Compose
The packaging layer. Wraps both services in isolated containers, connects them on a private network, injects secrets, and enforces start-up order with health checks.
docker-compose.yml Dockerfile ×2 nginx.conf .env
Message lifecycle

From keypress
to AI reply

Follow one message — "What is Python?" — through every layer of the stack.

Request trace
7 steps, ~300ms end-to-end
1
⚛️ React
You press Enter
ChatInput.jsx captures the keystroke. It calls sendMessage() from the useChat hook, which serialises your text into JSON.
2
🔌 WebSocket
Message flies over the wire
The open WebSocket connection instantly carries your message to Python. No new connection needed — the pipe is already live.
ws.send(JSON.stringify({ "type": "message", "content": "What is Python?" }))
3
🐍 FastAPI
Backend stores & echoes
main.py appends your message to the conversation history dict, then sends a user_message event back so React can show your bubble immediately.
4
🐍 FastAPI → Claude
Claude is called
FastAPI opens a streaming request to the Anthropic Claude API with the full conversation history as context. Claude starts generating.
# Python: open a streaming context with client.messages.stream(model="claude-sonnet-4", messages=[...]) as stream:
5
🔌 Streaming tokens
Words trickle in real-time
Each token (word fragment) Claude generates is immediately forwarded as a stream_chunk WebSocket event. This runs dozens of times per second.
for chunk in stream.text_stream: await ws.send_text({ "type": "stream_chunk", "chunk": chunk })
6
⚛️ React
Live render — typewriter effect
useChat.js appends each chunk to streamBuffer state. React re-renders the bubble with the growing text. You see words appear before Claude has finished.
7
🐍 FastAPI
Stream ends, message saved
Claude finishes. FastAPI saves the full reply to the conversation dict and sends a stream_end event. React finalises the bubble and clears streamBuffer.
WebSocket protocol

The message
vocabulary

Both sides speak JSON. Click the button to simulate a full conversation round-trip.

ws://localhost:8000/ws/abc123
● connected
Browser → Server (you send)
type: "message"
content: "What is Python?"
model: "claude-sonnet-4"
Server → Browser (you receive)
type: "user_message"
role: "user"
type: "stream_start"
— AI is generating —
type: "stream_chunk"
chunk: "Python is a"
type: "stream_chunk"
chunk: " high-level language"
type: "stream_end"
message: { role: "assistant", ... }
Infrastructure

How Docker
wires it up

Two containers, one private network, four magic tricks.

Host machine
chatgpt-network (internal DNS)
⚛️
frontend
:80
imagenginx:alpine
buildnode → npm build → copy dist
servesReact static files
proxies/ws/ → backend:8000
🐍
backend
:8000
imagepython:3.12-slim
envANTHROPIC_API_KEY=…
healthGET /health → 200
cmduvicorn main:app
"backend" resolves via DNS
1
Private DNS
Nginx can write http://backend:8000 — Docker resolves the name. No IPs needed.
2
Secret injection
Your API key lives in .env on your machine, injected at runtime — never baked into the image.
3
Healthcheck gating
Frontend won't start until backend passes GET /health → 200. No race conditions.
4
Multi-stage build
Node compiles React in stage 1. Only the tiny dist/ folder moves to the Nginx stage. No Node in production.
terminal — chatgpt-clone/
$ docker compose up --build
[+] Building 2/2
✔ backend Built in 12.3s
✔ frontend Built in 28.1s
[+] Running 2/2
✔ Container chatgpt-backend Healthy
✔ Container chatgpt-frontend Started
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000
Open http://localhost 🎉