Project 4~30 minutesWorked Example

URL Shortener

Interview Scenario: "Build a URL shortener like bit.ly. Users enter a long URL and get a short code. Visiting the short URL redirects to the original. Track click counts."

What You'll Learn

  • Generating unique short codes
  • HTTP redirects (301/302/307)
  • In-memory data storage patterns
  • URL validation basics
Loading...

Interview Mode: Simplified in-memory storage. Perfect for live coding.

Step 1: Backend Implementation

Let's create a simple FastAPI backend. No database needed - we'll store URLs in a dictionary. This is perfect for interviews where you need to move fast.

backend/main.py
1from fastapi import FastAPI, HTTPException
2from fastapi.middleware.cors import CORSMiddleware
3from fastapi.responses import RedirectResponse
4from pydantic import BaseModel
5import random
6import string
7
8app = FastAPI()
9
10app.add_middleware(
11 CORSMiddleware,
12 allow_origins=["http://localhost:3000"],
13 allow_credentials=True,
14 allow_methods=["*"],
15 allow_headers=["*"],
16)
17
18# In-memory storage (resets when server restarts)
19urls = {} # short_code -> {original_url, clicks}
20
21class URLCreate(BaseModel):
22 url: str
23
24def generate_short_code():
25 return ''.join(random.choices(string.ascii_letters + string.digits, k=6))
26
27@app.post("/api/shorten")
28def shorten_url(data: URLCreate):
29 short_code = generate_short_code()
30 urls[short_code] = {"original_url": data.url, "clicks": 0}
31 return {
32 "short_code": short_code,
33 "original_url": data.url,
34 "short_url": f"http://localhost:8000/r/{short_code}",
35 "clicks": 0
36 }
37
38@app.get("/r/{short_code}")
39def redirect_to_url(short_code: str):
40 if short_code not in urls:
41 raise HTTPException(status_code=404, detail="URL not found")
42 urls[short_code]["clicks"] += 1
43 return RedirectResponse(url=urls[short_code]["original_url"], status_code=307)
44
45@app.get("/api/urls")
46def get_all_urls():
47 return [
48 {
49 "short_code": code,
50 "original_url": data["original_url"],
51 "short_url": f"http://localhost:8000/r/{code}",
52 "clicks": data["clicks"]
53 }
54 for code, data in urls.items()
55 ]

Why no database?

In a 45-minute interview, setting up SQLAlchemy can eat 15+ minutes. Using a simple dict demonstrates the same concepts without the boilerplate. You can always mention: "In production, I'd use a database with unique constraints and proper collision handling."

The trade-off

This in-memory approach resets when the server restarts. That's fine for interviews! Switch to Production Mode above to see the full database setup.

Step 2: Frontend Implementation

frontend/app/page.tsx
1"use client"
2
3import { useState, useEffect } from "react"
4
5interface ShortenedURL {
6 short_code: string
7 original_url: string
8 short_url: string
9 clicks: number
10}
11
12export default function URLShortener() {
13 const [url, setUrl] = useState("")
14 const [urls, setUrls] = useState<ShortenedURL[]>([])
15 const [loading, setLoading] = useState(false)
16 const [copied, setCopied] = useState<string | null>(null)
17
18 useEffect(() => {
19 fetchUrls()
20 }, [])
21
22 const fetchUrls = async () => {
23 const res = await fetch("http://localhost:8000/api/urls")
24 const data = await res.json()
25 setUrls(data)
26 }
27
28 const shortenUrl = async (e: React.FormEvent) => {
29 e.preventDefault()
30 if (!url) return
31
32 setLoading(true)
33 try {
34 const res = await fetch("http://localhost:8000/api/shorten", {
35 method: "POST",
36 headers: { "Content-Type": "application/json" },
37 body: JSON.stringify({ url }),
38 })
39
40 if (!res.ok) throw new Error("Invalid URL")
41
42 const data = await res.json()
43 setUrls([data, ...urls])
44 setUrl("")
45 } catch (err) {
46 alert("Please enter a valid URL")
47 }
48 setLoading(false)
49 }
50
51 const copyToClipboard = (shortUrl: string) => {
52 navigator.clipboard.writeText(shortUrl)
53 setCopied(shortUrl)
54 setTimeout(() => setCopied(null), 2000)
55 }
56
57 return (
58 <main className="min-h-screen p-8 max-w-3xl mx-auto">
59 <h1 className="text-3xl font-bold mb-2">URL Shortener</h1>
60 <p className="text-gray-600 mb-6">Paste a long URL to get a short link</p>
61
62 <form onSubmit={shortenUrl} className="flex gap-2 mb-8">
63 <input
64 type="url"
65 value={url}
66 onChange={(e) => setUrl(e.target.value)}
67 placeholder="https://example.com/very/long/url/here"
68 className="flex-1 border p-3 rounded"
69 required
70 />
71 <button
72 type="submit"
73 disabled={loading}
74 className="bg-blue-500 text-white px-6 py-3 rounded hover:bg-blue-600
75 disabled:bg-gray-400"
76 >
77 {loading ? "..." : "Shorten"}
78 </button>
79 </form>
80
81 <div className="space-y-3">
82 {urls.map((item) => (
83 <div key={item.short_code}
84 className="p-4 bg-gray-50 rounded flex items-center gap-4">
85 <div className="flex-1 min-w-0">
86 <p className="font-mono text-blue-600 font-medium">
87 {item.short_url}
88 </p>
89 <p className="text-sm text-gray-500 truncate">
90 {item.original_url}
91 </p>
92 </div>
93 <div className="text-sm text-gray-500">
94 {item.clicks} clicks
95 </div>
96 <button
97 onClick={() => copyToClipboard(item.short_url)}
98 className="px-3 py-1 text-sm border rounded hover:bg-gray-100"
99 >
100 {copied === item.short_url ? "Copied!" : "Copy"}
101 </button>
102 </div>
103 ))}
104 </div>
105 </main>
106 )
107}

Exercises

What You Learned

FastAPI Basics

  • In-memory data storage with dicts
  • Short code generation
  • HTTP redirects (307)
  • CORS middleware setup

React Frontend

  • Form submission with fetch POST
  • Clipboard API for copy functionality
  • Optimistic UI updates
  • Loading and feedback states

Ready for Real-time?

Next up: WebSockets. You'll build a real-time chat application where messages appear instantly without refreshing.

Project 5: Real-time Chat