Inspiration

We wanted to build something deceptively simple — a URL shortener — and treat it like a real production system. The challenge wasn't the feature, it was the engineering: how do you make it reliable, fast, and scalable under real load?

What it does

URL Shortener — a production-grade link shortening service with a full REST API.

Users can submit any long URL and receive a short code (e.g. http://206.189.59.175:8000/aB3xZ9). Accessing the short link redirects instantly to the original URL. The system tracks every redirect as a click event, stores user and URL records, and exposes full CRUD endpoints for managing users, links, and events.

Core features:

  • POST /shorten — generate a short URL with an auto-generated or custom short code
  • GET / — redirect to the original URL (Redis-cached for speed)
  • POST /urls — create a URL with title, user ownership, and metadata
  • GET /users, POST /users — manage user accounts with email validation
  • GET /events — query click events filtered by URL, user, or event type
  • Automatic click tracking on every redirect
  • Graceful Redis fallback — if cache is unavailable, falls back to the database
  • Full input validation — rejects malformed requests, invalid emails, bad URL schemes
  • GET /health — health check endpoint for monitoring

How we built it

We started with a minimal Flask app and iterated through three engineering tiers for both reliability and scalability:

Reliability track: Added a /health endpoint, built a GitHub Actions CI pipeline, wrote 100+ tests with 95% coverage, implemented structured error handling, and configured restart: always for chaos resilience.

Scalability track: Ran k6 load tests at 50, 200, and 500 concurrent users. Fixed bottlenecks at each stage — moving from a single Flask process to Gunicorn workers, adding Nginx load balancing across two app containers, adding connection pooling, and finally Redis caching for redirect lookups.

Deployment: Manually provisioned a DigitalOcean Droplet (CentOS 9), installed Docker, and ran the full stack via Docker Compose — live at http://206.189.59.175:8000.

Challenges we ran into

  • Nginx crash-looping because it couldn't resolve app container DNS before they fully joined the Docker network
    • gevent async workers breaking psycopg2 due to C extension incompatibility — had to revert to sync workers
    • PostgreSQL connection exhaustion under 200 VUs before we added connection pooling
    • CI test failures caused by patching the wrong class (PostgresqlDatabase vs PooledPostgresqlDatabase)

Accomplishments that we're proud of

What we learned

We passed majority tests, that's no small feat

  • How horizontal scaling with Nginx and multiple app instances actually works at the infrastructure level
  • The difference between connection pooling and opening raw DB connections — and why it matters under load
  • How Redis caching dramatically reduces database pressure on hot read paths
  • How to provision and deploy a production server on DigitalOcean from scratch — no managed platforms, just a Linux VM and Docker
  • How CI/CD gates (coverage thresholds, test requirements) enforce quality before code reaches production

What's next for PE hackathon 2026 - Production grade url shortener

review code and continuous learning

Share this project:

Updates