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, HTTPException2from fastapi.middleware.cors import CORSMiddleware3from fastapi.responses import RedirectResponse4from pydantic import BaseModel5import random6import string78app = FastAPI()910app.add_middleware(11 CORSMiddleware,12 allow_origins=["http://localhost:3000"],13 allow_credentials=True,14 allow_methods=["*"],15 allow_headers=["*"],16)1718# In-memory storage (resets when server restarts)19urls = {} # short_code -> {original_url, clicks}2021class URLCreate(BaseModel):22 url: str2324def generate_short_code():25 return ''.join(random.choices(string.ascii_letters + string.digits, k=6))2627@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": 036 }3738@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"] += 143 return RedirectResponse(url=urls[short_code]["original_url"], status_code=307)4445@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"23import { useState, useEffect } from "react"45interface ShortenedURL {6 short_code: string7 original_url: string8 short_url: string9 clicks: number10}1112export 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)1718 useEffect(() => {19 fetchUrls()20 }, [])2122 const fetchUrls = async () => {23 const res = await fetch("http://localhost:8000/api/urls")24 const data = await res.json()25 setUrls(data)26 }2728 const shortenUrl = async (e: React.FormEvent) => {29 e.preventDefault()30 if (!url) return3132 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 })3940 if (!res.ok) throw new Error("Invalid URL")4142 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 }5051 const copyToClipboard = (shortUrl: string) => {52 navigator.clipboard.writeText(shortUrl)53 setCopied(shortUrl)54 setTimeout(() => setCopied(null), 2000)55 }5657 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>6162 <form onSubmit={shortenUrl} className="flex gap-2 mb-8">63 <input64 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 required70 />71 <button72 type="submit"73 disabled={loading}74 className="bg-blue-500 text-white px-6 py-3 rounded hover:bg-blue-60075 disabled:bg-gray-400"76 >77 {loading ? "..." : "Shorten"}78 </button>79 </form>8081 <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} clicks95 </div>96 <button97 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