Chirag Chaudharyopen to internships & junior roles

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

short.link — four layers, top to bottomfig. 01 · 4 layers · ↑↓ or click to isolate
layer 01 of 04 · interface

what the person using it touches

/client · React 19 SPA on VercelVITE_API_BASE_URL → VPS
https://github.com/chaudhary64/short.link/custom-slugShorten →TANSTACK QUERY · CLICK TELEMETRY CACHEshort.link/gh-repo → 302 (IN · Chrome · Desktop)Guest session active · 3 links ready to claim on OAuth / signupBRANDED QR EXPORTSVG / PNGclient-rendered
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
POST /api/*Zod schema gateRedis counter429 on burstJWT + Refreshhashed session rowClaim flowguest → user_idRevoking 1 session deletes its hash in PostgreSQL without invalidating other devices.
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
GET /:slugnanoid codeRedis lookupHIT → 302 instantMISS → Drizzle SELECT linksrepopulates Redis TTL cacheASYNC TELEMETRY BRANCH (NEVER BLOCKS 302 REDIRECT)geoip-lite (country) + ua-parser-js (OS/device) → 1 batched INSERT INTO clicks
schema
PostgreSQL with Drizzle. Tables are shaped for moving ownership, not just creating it, because of the guest claim flow.
clicks
geoip-lite and ua-parser-js resolve 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
git push mainGitHub ActionsARM64 imageDocker buildxVPS + Traefikauto Let’s EncryptHealth probeFAIL → rollbackClient SPA served from Vercel CDN · API & /:slug redirects served from VPS
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

© 2026 Chirag Chaudhary

back to the top ↑