Build a URL Shortener — Three Ways
Build the same URL Shortener microservice using Claude Code, then Gemini CLI, then GitHub Copilot CLI. Document your experience at each step. By the end, you'll have first-hand evidence — not theory — for which tool works best for your workflow.
What You'll Build
A URL shortener is the ideal capstone project: small enough to complete in a single session with each tool, yet rich enough to exercise every capability — scaffolding, database design, API design, authentication, testing, documentation, and security review.
| Feature | Specification |
|---|---|
| POST /shorten | Accept a long URL, generate a 6-char alphanumeric code, store in SQLite, return short URL |
| GET /{code} | Look up code in SQLite, return HTTP 301 redirect to long URL |
| GET /stats/{code} | Return click count, creation date, original URL for a given code |
| Click analytics | Increment click counter on every redirect; store timestamp of each click |
| Error handling | 404 for unknown codes, 422 for invalid URLs, 429 for rate limiting (optional stretch goal) |
| Tests | pytest tests covering all three endpoints, including edge cases |
| Documentation | README.md with setup instructions + auto-generated OpenAPI spec |
Python 3.11+, Node.js 18+ (for some Claude Code tools), git. Claude Code installed and authenticated. Gemini CLI installed and authenticated. GitHub CLI with Copilot extension installed (gh extension install github/gh-copilot). VS Code with Copilot extension for Round 3. Estimated time: 60–90 minutes per round.
It has a clear spec (no ambiguity), requires real database design (not just hello world), has obvious security concerns (URL validation, redirect safety, rate limiting), and the full feature set takes 45–90 minutes with each tool — long enough to learn something, short enough to complete. It also has a well-understood reference implementation so you can evaluate AI output quality accurately.
Set up a fresh directory before each round. This simulates starting from scratch with each tool:
# Create three separate directories — one per round
New-Item -ItemType Directory -Force url-shortener-claude
New-Item -ItemType Directory -Force url-shortener-gemini
New-Item -ItemType Directory -Force url-shortener-copilot
# Track your observations in a journal file
New-Item -ItemType File url-shortener-journal.md
Add-Content url-shortener-journal.md "# URL Shortener Capstone Journal`n`n## Round 1: Claude Code`n`n## Round 2: Gemini CLI`n`n## Round 3: Copilot CLI`n"
# Create three separate directories — one per round
mkdir url-shortener-{claude,gemini,copilot}
# Track your observations in a journal file
cat > url-shortener-journal.md << 'EOF'
# URL Shortener Capstone Journal
## Round 1: Claude Code
## Round 2: Gemini CLI
## Round 3: Copilot CLI
EOF
Round 1: Claude Code
Start the timer. Navigate to url-shortener-claude/ and run Claude Code. Follow these four steps in order, documenting your observations after each one.
Set-Location url-shortener-claude
claude "scaffold a FastAPI URL shortener with SQLite backend. Requirements:
- POST /shorten: accept long_url, generate 6-char alphanumeric code, return short_url
- GET /{code}: 301 redirect to original URL, increment click count
- GET /stats/{code}: return clicks, created_at, original URL
- SQLAlchemy for SQLite ORM
- pydantic models for request/response validation
- Full error handling: 404, 422, rate limit stub
- requirements.txt and uvicorn startup command
Make it production-ready, not a toy."
cd url-shortener-claude
claude "scaffold a FastAPI URL shortener with SQLite backend. Requirements:
- POST /shorten: accept long_url, generate 6-char alphanumeric code, return short_url
- GET /{code}: 301 redirect to original URL, increment click count
- GET /stats/{code}: return clicks, created_at, original URL
- SQLAlchemy for SQLite ORM
- pydantic models for request/response validation
- Full error handling: 404, 422, rate limit stub
- requirements.txt and uvicorn startup command
Make it production-ready, not a toy."
pip install -r requirements.txt && uvicorn main:app --reload.claude "add pytest tests for all endpoints. Include:
- Test POST /shorten with valid URL
- Test POST /shorten with invalid URL (expect 422)
- Test GET /{code} redirect with valid code
- Test GET /{code} with unknown code (expect 404)
- Test GET /stats/{code} click count increments correctly
- Test that duplicate URLs can be shortened (multiple codes allowed)
Use pytest fixtures for test database isolation."
pytest -v. Record: how many tests pass on first run? Did it need iterations to fix failures?claude "review this URL shortener code for security issues. Look for:
1. Open redirect vulnerabilities (are we validating that URLs are safe?)
2. SQLite injection risks
3. Missing rate limiting on /shorten endpoint
4. Short code collision probability and birthday attack risk
5. Analytics data privacy (storing IPs?)
6. Any other security concerns for a production deployment
For each issue found: explain the risk, show the vulnerable code, and provide a fix."
claude "generate:
1. A complete README.md with: project description, prerequisites, installation, usage examples with curl commands, API reference table
2. Add FastAPI docstrings to all endpoints with example request/response JSON
3. A .env.example file listing all configurable environment variables
Make the README good enough that a new developer can set this up in 5 minutes."
Round 2: Gemini CLI
Reset the timer. Navigate to url-shortener-gemini/. You'll use Gemini CLI's Plan Mode for the scaffold step and direct prompts for the remaining three.
Set-Location url-shortener-gemini
# WHAT: Start Gemini CLI in Plan Mode — it proposes architecture before coding
# WHY: Plan Mode lets you review and edit the approach before execution
# GOTCHA: Type 'plan' at the prompt, then enter your task description
gemini
# At the GEMINI prompt, type:
# plan scaffold a FastAPI URL shortener with SQLite, same spec as before:
# POST /shorten, GET /{code} redirect, GET /stats/{code}, full error handling,
# SQLAlchemy ORM, pydantic models, requirements.txt. Production quality.
# Review the plan Gemini proposes.
# Type 'approve' to execute, or edit the plan first.
cd url-shortener-gemini
# WHAT: Start Gemini CLI in Plan Mode — it proposes architecture before coding
# WHY: Plan Mode lets you review and edit the approach before execution
# GOTCHA: Type 'plan' at the prompt, then enter your task description
gemini
# At the GEMINI prompt, type:
# plan scaffold a FastAPI URL shortener with SQLite, same spec as before:
# POST /shorten, GET /{code} redirect, GET /stats/{code}, full error handling,
# SQLAlchemy ORM, pydantic models, requirements.txt. Production quality.
# Review the plan Gemini proposes.
# Type 'approve' to execute, or edit the plan first.
skill.md file that defines your testing standards, then reference it in your Gemini prompt. This demonstrates Gemini's context injection pattern.# First: create the skill.md file
# In PowerShell: New-Item -ItemType File skill.md then edit it
# In Bash: create with your editor or:
cat > skill.md << 'EOF'
# Testing Standards
All tests must:
- Use pytest fixtures for database isolation (test_db fixture)
- Cover the happy path AND at least 2 error cases per endpoint
- Assert on HTTP status codes AND response body fields
- Use TestClient from fastapi.testclient
- Run in under 5 seconds total
EOF
# Then in Gemini CLI:
gemini "following @skill.md, add pytest tests for all three endpoints.
Same coverage requirements: POST /shorten valid + invalid,
GET redirect valid + 404, GET /stats with count increment verification."
gemini "review this URL shortener for security issues:
1. Open redirect vulnerabilities
2. SQL injection risks
3. Missing rate limiting
4. Short code collision/birthday attack risk
5. Any other production security concerns
For each issue: show the vulnerable code, explain the risk, provide a fix."
gemini "generate: (1) complete README.md with install + usage + curl examples + API reference,
(2) FastAPI docstrings with example JSON on all endpoints,
(3) .env.example file. Target: new developer running in 5 minutes."
Round 3: GitHub Copilot CLI
Round 3 is the most instructive because it shows the gap. GitHub Copilot CLI (gh copilot) is not a code generation tool — it is a shell command assistant. You will need to use VS Code with Copilot Chat for the actual code generation. This round documents where Copilot excels and where manual work remains.
This round takes longer because Copilot CLI cannot autonomously scaffold a FastAPI project. You will use VS Code + Copilot Chat for code generation (this is Copilot's actual strength), and gh copilot suggest for shell commands. Copilot CLI's terminal experience is the surface this course covers — and this round shows exactly where its limits are.
gh copilot CLI cannot scaffold a project. Open VS Code, open url-shortener-copilot/, and use the Copilot Chat panel (Ctrl+Shift+I) with the same scaffolding prompt. This is Copilot's actual home — the IDE.# In VS Code Copilot Chat (Ctrl+Shift+I), type:
# "scaffold a FastAPI URL shortener with SQLite backend.
# POST /shorten, GET /{code} redirect, GET /stats/{code}.
# SQLAlchemy ORM, pydantic models, full error handling, requirements.txt."
# Note what Copilot Chat generates vs. what claude/gemini generated.
# Does it create files? Does it ask for workspace access?
# For shell commands during setup, use gh copilot:
gh copilot suggest -t shell "create a Python virtual environment and install fastapi sqlalchemy uvicorn"
# → Copilot suggests: python -m venv venv && source venv/bin/activate && pip install fastapi sqlalchemy uvicorn
# You approve and run it.
test_main.py in VS Code and start typing a test function — Copilot will autocomplete full test implementations. This is faster than any prompting workflow for test generation IF you have the context open in the IDE.# Open test_main.py in VS Code. Type the function signature:
# Copilot will autocomplete the entire test body as you type.
import pytest
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_shorten_valid_url():
# Start typing here — Copilot will suggest the full test body
# based on what it sees in main.py (if that file is open in the editor)
...
def test_shorten_invalid_url():
# Again, let Copilot complete — notice how accurate it is with context
...
gh copilot suggest for the security review. Notice what it returns — a shell command, not a code review. This is the fundamental limitation of the CLI surface.# ATTEMPT: Use gh copilot for security review
gh copilot suggest -t shell "review main.py for open redirect vulnerabilities and SQL injection"
# ACTUAL OUTPUT: A shell command like:
# grep -n "redirect\|sql\|query" main.py
# NOT a security review — just a grep command
# WHAT WORKS INSTEAD: Use VS Code Copilot Chat for code review
# In VS Code Chat: "Review main.py for open redirect vulnerabilities,
# SQL injection risks, and rate limiting gaps"
# This gives a real review — but requires the IDE, not the terminal
# ATTEMPT: Use gh copilot for security review
gh copilot suggest -t shell "review main.py for open redirect vulnerabilities and SQL injection"
# ACTUAL OUTPUT: A shell command like:
# grep -n "redirect\|sql\|query" main.py
# NOT a security review — just a grep command
# WHAT WORKS INSTEAD: Use VS Code Copilot Chat for code review
# In VS Code Chat: "Review main.py for open redirect vulnerabilities,
# SQL injection risks, and rate limiting gaps"
# This gives a real review — but requires the IDE, not the terminal
gh copilot suggest actually return? How did VS Code Copilot Chat's review compare to Claude Code's review in depth and actionability?gh copilot explain on individual commands, and VS Code Copilot Chat for the README. Note where Copilot's strengths compensate for its CLI limitations.# gh copilot explain is excellent for explaining commands
gh copilot explain "uvicorn main:app --reload --host 0.0.0.0 --port 8000"
# → Clear explanation of each flag. This is where gh copilot is genuinely useful.
# For README generation, use VS Code Chat:
# "Write a README.md for this FastAPI URL shortener. Include:
# prerequisites, installation steps, curl examples, API reference"
# Then copy-paste from VS Code to README.md manually.
# The gap: Claude Code and Gemini wrote README.md directly to disk.
# Copilot Chat requires manual copy-paste to file.
# This is the fundamental IDE-vs-terminal distinction.
# gh copilot explain is excellent for explaining commands
gh copilot explain "uvicorn main:app --reload --host 0.0.0.0 --port 8000"
# → Clear explanation of each flag. This is where gh copilot is genuinely useful.
# For README generation, use VS Code Chat:
# "Write a README.md for this FastAPI URL shortener. Include:
# prerequisites, installation steps, curl examples, API reference"
# Then copy-paste from VS Code to README.md manually.
# The gap: Claude Code and Gemini wrote README.md directly to disk.
# Copilot Chat requires manual copy-paste to file.
# This is the fundamental IDE-vs-terminal distinction.
Scoring Rubric
After completing all three rounds, fill in your scores below. The reference scoring reflects typical results — your experience may differ based on how you wrote prompts and which model tier you used.
Copilot's test completion in VS Code often matches or exceeds Claude and Gemini — because it has full codebase context in the IDE and its training data includes massive amounts of pytest/unittest patterns. The CLI limitation does not affect IDE-based test writing. This is the hybrid stack insight in action: use the right tool for each job.
Your Verdict
Fill in your own scores and reflections. These prompts are designed to surface insights about your specific workflow — not to confirm the "expected" answers.
Your Capstone Reflection
Responses are saved to localStorage in this browser only — not sent anywhere.
Next Steps
You have completed the AI CLI Tools Compared course and capstone. Here is where to go next:
The most valuable outcome of this capstone is your personal comparison data. Share your journal (url-shortener-journal.md) and scoring rubric in the community Discord. Over time, the community's aggregated results will reveal real-world performance patterns that no single reviewer can observe. Your data contributes to a collective understanding of where each tool excels.
- Completed Round 1 with Claude Code (all 4 steps)
- Completed Round 2 with Gemini CLI (all 4 steps)
- Completed Round 3 with GitHub Copilot CLI + VS Code (all 4 steps)
- Filled in scoring rubric with your actual scores
- Completed the verdict reflection with honest observations
- Saved journal to
url-shortener-journal.md - Decided on your personal tool stack going forward