A production-shaped Flask URL shortener: app-factory pattern, Blueprints, Flask-SQLAlchemy, and a real Redis cache-aside layer in front of Postgres, with per-link click analytics.
Nobody has started this yet — be first.
No published tickets in this project yet.
About this project
A small, production-shaped Flask app: shorten URLs, redirect on them, and see click analytics per link. The point of this project is not scale — it is doing the app factory pattern, Blueprints, Flask-SQLAlchemy, and a real Redis cache-aside layer the way a production Flask app would from day one. instantiates , , and a Redis wrapper without an app attached — only (in ) binds them, via etc., so multiple independent app instances (a test app, a CLI-only app, a worker) never share state. Routes are split by concern into Blueprints (, ), neither of which imports a bare object — they only see and the shared / extensions. The hot path, , is a textbook cache-aside: check Redis for first, fall back to Postgres on a miss, cache the result with a TTL, and always insert a on a successful redirect (hit or miss) since the cache only ever short-circuits the slug lookup, never the analytics write. Deletion and expiry both have explicit invalidation rules — a deleted link 404s immediately rather than waiting out a TTL, and an embedded in the cached JSON is re-checked on every hit so a link that is set to expire in 30 seconds cannot keep serving stale for the rest of an hour-long cache TTL. Click analytics hash visitor IPs () rather than storing them raw, so the app can tell "one visitor clicked 5 times" from "5 different visitors" without keeping anything that maps back to a real IP.
Clone it
$git clone https://github.com/nishant1821/FlaskDevTraining.gitHow to run it locally, step by step.
app/extensions.pydb = SQLAlchemy()migrate = Migrate()create_app()app/__init__.pydb.init_app(app)app/blueprints/urlsapp/blueprints/analyticsappcurrent_appdbredis_clientGET /<slug>shorturl:slug:<slug>ClickEventexpires_atsha256(secret_key + ":" + ip)Twenty practice tickets are injected into an otherwise-working codebase: twelve are real, minimal, mostly single-mistake bugs (a base62 encoder that never reverses its digit order, a boundary check using strict instead of inclusive comparisons, a salt argument silently ignored by a hash function, an analytics query missing the one .filter() its neighbors all have, an off-by-one day in a rolling time window, a cache invalidation call that is simply never made, a DB fallback query missing an is_active filter, a TTL calculation with no floor that crashes Redis on a link expiring within a second, datetime.now() used where every other timestamp in the codebase uses datetime.utcnow(), a check-then-act race in slug-collision handling with no exception safety net around the commit, and a Redis extension that is a shared singleton instead of being scoped per Flask app instance), and eight are half-built enhancements — a listing endpoint, an update endpoint, a rate limiter, a custom analytics date range, a denormalized click-count column that already exists but is never incremented, a per-link cache TTL override, a bulk-create endpoint, and graceful degradation when Redis is unreachable — each currently a stub, a 501/NotImplementedError, or simply missing wiring, never a disguised version of the real fix. Ordered easy to hard, each ticket has its own currently-failing pytest test that goes green when (and only when) that ticket is fixed; no solutions are given anywhere in the ticket docs.