URL shorteners look simple from the outside. Paste a long URL, get a short one back. But building one that also tracks clicks per link, generates QR codes, and scopes everything to authenticated users turns out to be a solid full-stack exercise.
QuickLink is live at quicklink-aymane.vercel.app and the source is on GitHub.
Why Build This
I wanted a project that required a real backend with auth, not just a frontend talking to a public API. URL shorteners have clear data relationships (users → links → clicks), which makes them a good test of how well you can model a database and write clean queries.
Stack
Supabase was the core choice. It gave me a Postgres database, row-level security for per-user data isolation, and auth (email + OAuth) out of the box — without running my own server. The realtime subscriptions were a bonus I used for the live click counter.
React + TypeScript + React Router v7 for the frontend. The app has distinct pages: a dashboard listing all your links, a detail page showing click analytics per link, and a public redirect route.
Context API for auth state. I kept this simple — a single AuthContext that wraps the app and exposes the current user. No need for Redux here since auth state is global but rarely changes.
qrcode.js for QR code generation. Each shortened link gets a downloadable QR code rendered in a canvas element client-side — no server needed for this part.
Mantine UI for the interface, consistent with my other projects.
Database Design
The schema is three tables:
users (managed by Supabase Auth)
links
id, user_id, original_url, short_code, created_at
clicks
id, link_id, clicked_at, country, device
Row-level security policies on links and clicks ensure users can only read and write their own data. This runs at the database level — even if there's a bug in my application code, a user can never access another user's links.
The Redirect Route
The public redirect at /:shortCode needs to be fast. When someone visits a shortened URL:
- Look up the
short_codein the database - Log a new row in
clickswith timestamp and metadata - Redirect to the original URL
The click logging runs asynchronously so it doesn't add latency to the redirect. The user lands on the target page while the click is being recorded in the background.
Analytics Dashboard
Each link has a detail page showing clicks over time (a line chart), top devices, and top countries. The data comes from aggregating the clicks table grouped by day, device type, and country.
Writing these aggregation queries directly in Supabase's query builder was one of the more interesting parts of the project — it's closer to writing SQL than most ORM abstractions.
What I Learned
Supabase's row-level security is powerful but the mental model takes time to internalize. You're writing PostgreSQL policies that run on every query — it's not application-level filtering. Once it clicked, it felt like the right place to put authorization logic.
QR code generation client-side with a canvas is surprisingly clean. No dependency on an external API, works offline, and the user can download the image directly from the browser.