Project 5~90 minutesGuided Practice

Real-time Chat

Interview Scenario: "Build a simple chat application. Messages should appear in real-time without refreshing the page. Multiple users should be able to chat together."

WebSockets vs HTTP

  • HTTP: Client asks, server responds, connection closes
  • WebSocket: Connection stays open, both sides can send anytime

Use WebSockets for: real-time updates, live notifications, collaborative editing, or any feature where the server needs to push data to the client.

Loading...

Backend with WebSockets

Terminal
1pip install websockets
backend/main.py
1from fastapi import FastAPI, WebSocket, WebSocketDisconnect
2from fastapi.middleware.cors import CORSMiddleware
3from datetime import datetime
4from typing import List
5import json
6
7app = FastAPI()
8
9app.add_middleware(
10 CORSMiddleware,
11 allow_origins=["http://localhost:3000"],
12 allow_credentials=True,
13 allow_methods=["*"],
14 allow_headers=["*"],
15)
16
17
18class ConnectionManager:
19 def __init__(self):
20 self.active_connections: List[WebSocket] = []
21
22 async def connect(self, websocket: WebSocket):
23 await websocket.accept()
24 self.active_connections.append(websocket)
25
26 def disconnect(self, websocket: WebSocket):
27 self.active_connections.remove(websocket)
28
29 async def broadcast(self, message: dict):
30 """Send message to ALL connected clients"""
31 for connection in self.active_connections:
32 await connection.send_json(message)
33
34
35manager = ConnectionManager()
36
37
38@app.websocket("/ws/chat")
39async def websocket_endpoint(websocket: WebSocket):
40 await manager.connect(websocket)
41
42 await manager.broadcast({
43 "type": "system",
44 "message": "A new user joined the chat",
45 "timestamp": datetime.now().isoformat()
46 })
47
48 try:
49 while True:
50 data = await websocket.receive_json()
51 await manager.broadcast({
52 "type": "message",
53 "username": data.get("username", "Anonymous"),
54 "message": data.get("message", ""),
55 "timestamp": datetime.now().isoformat()
56 })
57 except WebSocketDisconnect:
58 manager.disconnect(websocket)
59 await manager.broadcast({
60 "type": "system",
61 "message": "A user left the chat",
62 "timestamp": datetime.now().isoformat()
63 })

Frontend WebSocket Client

frontend/app/page.tsx
1"use client"
2
3import { useState, useEffect, useRef } from "react"
4
5interface Message {
6 type: "message" | "system"
7 username?: string
8 message: string
9 timestamp: string
10}
11
12export default function Chat() {
13 const [messages, setMessages] = useState<Message[]>([])
14 const [input, setInput] = useState("")
15 const [username, setUsername] = useState("")
16 const [isConnected, setIsConnected] = useState(false)
17 const wsRef = useRef<WebSocket | null>(null)
18 const messagesEndRef = useRef<HTMLDivElement>(null)
19
20 useEffect(() => {
21 const ws = new WebSocket("ws://localhost:8000/ws/chat")
22
23 ws.onopen = () => {
24 setIsConnected(true)
25 }
26
27 ws.onmessage = (event) => {
28 const message = JSON.parse(event.data)
29 setMessages(prev => [...prev, message])
30 }
31
32 ws.onclose = () => {
33 setIsConnected(false)
34 }
35
36 wsRef.current = ws
37
38 return () => ws.close()
39 }, [])
40
41 useEffect(() => {
42 messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
43 }, [messages])
44
45 const sendMessage = (e: React.FormEvent) => {
46 e.preventDefault()
47 if (!input.trim() || !wsRef.current) return
48
49 wsRef.current.send(JSON.stringify({
50 username: username || "Anonymous",
51 message: input
52 }))
53
54 setInput("")
55 }
56
57 return (
58 <main className="min-h-screen p-8 max-w-2xl mx-auto">
59 <div className="flex items-center justify-between mb-4">
60 <h1 className="text-2xl font-bold">Chat Room</h1>
61 <span className={`text-sm ${isConnected ? "text-green-500" : "text-red-500"}`}>
62 {isConnected ? "● Connected" : "● Disconnected"}
63 </span>
64 </div>
65
66 <input
67 type="text"
68 value={username}
69 onChange={(e) => setUsername(e.target.value)}
70 placeholder="Your name"
71 className="w-full border p-2 rounded mb-4"
72 />
73
74 <div className="h-96 overflow-y-auto border rounded p-4 mb-4 bg-gray-50">
75 {messages.map((msg, i) => (
76 <div key={i} className={`mb-2 ${msg.type === "system" ? "text-gray-500 italic" : ""}`}>
77 {msg.type === "message" ? (
78 <>
79 <span className="font-bold">{msg.username}: </span>
80 <span>{msg.message}</span>
81 </>
82 ) : (
83 <span>{msg.message}</span>
84 )}
85 </div>
86 ))}
87 <div ref={messagesEndRef} />
88 </div>
89
90 <form onSubmit={sendMessage} className="flex gap-2">
91 <input
92 type="text"
93 value={input}
94 onChange={(e) => setInput(e.target.value)}
95 placeholder="Type a message..."
96 className="flex-1 border p-2 rounded"
97 disabled={!isConnected}
98 />
99 <button
100 type="submit"
101 disabled={!isConnected}
102 className="bg-blue-500 text-white px-4 py-2 rounded disabled:bg-gray-400"
103 >
104 Send
105 </button>
106 </form>
107 </main>
108 )
109}

The useRef Pattern

We use useRef for the WebSocket because we need to access the same connection instance across renders without triggering re-renders. useState would cause issues here.

Alternatives to WebSockets

If asked, mention these alternatives:
  • Server-Sent Events (SSE) — one-way server→client, simpler
  • Long Polling — HTTP fallback, less efficient
  • Socket.io — library that handles fallbacks automatically

Exercises

Final Challenge

The last project is a full interview simulation. You'll build a mini e-commerce app in 2 hours, just like a real coding interview.

Project 6: E-Commerce Mini