cutaway 01 · independent project · live in production
solo build
A URL shortener where the backend was the hard part.
- role
- Everything below — front end, API, schema and deploy. Solo, independent project.
- stack
- React 19 · Vite · Redux Toolkit · TanStack Query · Node · Express · PostgreSQL · Drizzle · Redis · Docker
- runs on
- Vercel (/client SPA) + a single ARM64 VPS behind Traefik with automatic TLS (/server API & redirects).
- status
- Live in production · source public
- see it
- Open the live app · Source
The cutaway
pick a layer to inspect its artifact & decisions
layer 01 of 04 · interface
what the person using it touches
/client · React 19 SPA on VercelVITE_API_BASE_URL → VPS
- the app
- Shortens a link, manages custom aliases, exports branded QR codes, and shows a click-analytics dashboard.
- client state
- Redux Toolkit holds the session; TanStack Query holds everything that comes off the server.
- signing in
- Google OAuth 2.0 and email verification. The session lives in an httpOnly cookie rather than in JavaScript.
- guest first
- You can shorten a link before you have an account, and links made as a guest keep their click history when you claim them at signup.
layer 02 of 04 · api
the contract between them and the data
/server · Express REST boundarySameSite=None; Secure httpOnly cookie
- the server
- Express, with every request validated at the boundary using Zod before it reaches a handler.
- sessions
- JWT access tokens with rotating refresh tokens and hashed sessions. One device can be revoked without signing the others out.
- the claim
- Because a guest can already own links, the API has to move ownership when they sign up — that flow is the reason the schema is shaped the way it is.
- rate limits
- Redis holds the counters, so a burst is rejected before it reaches the database.
layer 03 of 04 · data
the shape the information settles into
GET /:slug hot path vs. off-path telemetryPostgreSQL + Drizzle ORM + Redis
- schema
- PostgreSQL with Drizzle. Tables are shaped for moving ownership, not just creating it, because of the guest claim flow.
- clicks
geoip-liteandua-parser-jsresolve country, device, browser and OS locally, so recording a click costs zero external API calls.- cache
- Redis serves redirect lookups and holds the rate-limit counters.
layer 04 of 04 · infrastructure
what keeps it running when nobody is watching
.github/workflows · ARM64 release pipelineTraefik TLS + automated rollback
- build
- GitHub Actions builds an ARM64 image.
- deploy
- It ships to a VPS behind Traefik, which handles automatic TLS.
- rollback
- A post-deploy check runs after release. If it fails, the deploy rolls itself back.
Constraints
what the build had to work around
- no platform
- There is no platform doing the boring parts here. TLS, routing and a safe release are this project's own problem, which is why Traefik and the rollback gate live in the repository instead of in a dashboard.
- nothing on the hot path
- A redirect should not wait on somebody else's API, so geo and device data are resolved locally rather than looked up.
- a guest can own data
- Allowing links before an account exists is a product decision that becomes a schema decision. The claim flow had to be designed before the tables were.
Other cutaways
two more
02NutriGaininspect nutrigain cutaway →A tracker that turns logged volume into charts, on a design system that holds AA contrast in both themes.01Spain Collection — a rebuild studyinspect spain collection study →A rebuild study: someone else's design, rebuilt to learn scroll choreography from the inside (2 layers).↖All work & studiesback to the showcase →