ShortStack is a full-stack URL shortener built with Node.js, Express 5, and PostgreSQL, deployed on a Hostinger VPS via Coolify. It implements a cache-aside pattern with Redis to serve redirects at low latency, and decouples click recording from the redirect path using BullMQ async job queues — so analytics never slow down the user. The infrastructure layer includes a multi-stage Docker build, an Nginx reverse proxy, and Traefik handling SSL termination, forming a production-grade request chain. A multi-tenant auth API built with JWT and bcrypt allows the same service to support multiple independent applications. The project was built deliberately from primitives — raw SQL, no ORM, no framework magic — to develop deep understanding of each layer.

A minimal React single-page app served directly by Express from the built static files. Users paste a long URL and receive a short link in the same domain (e.g. shortstack.lawrenceamlangomes.com/abc123).
The interface handles loading states, validation errors from the API, and a one-click copy-to-clipboard action with visual feedback.

Visiting a short URL (e.g. /abc123) triggers a 301 redirect to the original URL. The redirect handler checks Redis first — on a cache HIT the response skips the database entirely. On a MISS it queries PostgreSQL, populates the cache with a 24-hour TTL, and redirects.
An X-Cache: HIT/MISS response header exposes cache behaviour for observability, following the CDN industry convention.

Every redirect asynchronously records a click event via BullMQ. The redirect response is sent immediately — the analytics write happens in a background worker, so the user never waits for the database INSERT.
Click data is stored in a separate normalised clicks table (not a counter column), enabling time-series queries. The /api/links/:slug/stats endpoint returns the total click count for any slug.

A JWT-based authentication API with an app field on every user, allowing the same email to register independently for different applications. The UNIQUE(email, app) constraint enforces this at the database level.
Passwords are hashed with bcrypt (cost factor 12). JWT tokens are issued in the response body with a 7-day expiry.

The full production request chain is: Internet → Traefik (SSL termination, port 443) → Nginx (reverse proxy, port 80, internal) → Express (port 3000, internal). Nginx and the app run as separate Docker services defined in docker-compose.yml and deployed via Coolify's docker-compose buildpack.
The Nginx config uses Docker's internal DNS resolver (127.0.0.11) with a variable upstream to force runtime DNS resolution — preventing the default behaviour where Nginx caches upstream IPs at startup and breaks when containers restart.
