Skip to content
Piyush ๐Ÿ‘‹
all writing

I built my own bitly โ€” with the analytics Bitly hides.

Feb 2026 ยท 7 min read

Every link I share is piyus.site/โ€ฆ, which means somewhere there's a service turning short codes into redirects. Commercial shorteners do this fine โ€” and then charge you to see who clicked. So I built my own: FastAPI, server-rendered HTML via HTMX, SQLAlchemy, Docker. No SPA, no build step for the frontend. A form posts, HTML comes back.

The route table is the whole product: POST /shorten mints a code, POST /custom_shorten lets you claim your own slug, GET /{short_code} 302s to the target, GET /h/{name} serves a public ask page, and GET /ana is the analytics dashboard sitting behind HTTP basic auth. Five routes. Nothing else was needed, so nothing else exists.

The part I'm proudest of is invisible: every redirect fires a background task that logs the visit โ€” IP-derived location, user agent, referrer โ€” after the 302 is already on its way. Analytics must never slow down the redirect; the redirect is the product, the analytics are the exhaust. FastAPI's BackgroundTasks is exactly the right weight for this โ€” a queue would be more correct and entirely unnecessary at my scale, and the code admits that.

Unusual for a weekend project: it has test scripts with opinions. The repo carries verify_analytics.py, verify_custom_shorten.py, verify_ask_page.py, and a verify_regression.py that ties them together. I wrote them because shorteners fail silently โ€” a broken redirect looks exactly like a working page until someone tells you their link is dead. The scripts are the monitoring.

Honest limits: basic auth protects the dashboard but it's one shared password; background tasks die with the process (a crash eats visits); and there's no abuse throttling yet. If it ever outgrows one container, the queue and the rate limiter are the first two things I'd add โ€” in that order.