URL Shortener with Real-Time Analytics
A production-grade Bitly-style URL shortener with real-time analytics. Users paste a long URL and get a short one. A dashboard shows click counts, geo data, referrer sources, and device breakdown — all updated in real time via async click tracking.
Estimated monthly cost: ~$33–58/mo | Runtime: Node.js 20 | Orchestration: Kubernetes on AWS EKS | Region: us-east-1
How it works
Shorten flow — The Next.js frontend sends the long URL to a Fastify API. The API validates the input, generates a nanoid slug, writes to Redis (24hr TTL cache) and PostgreSQL, then returns the short link to the user.
Redirect + tracking flow — When a visitor clicks the short link, the API checks Redis first (cache hit ~1ms), falls back to PostgreSQL on a miss, then issues a 302 redirect. A BullMQ job is immediately enqueued so a background worker can parse geo data, detect device type, and record the click — all without delaying the redirect response.
Services used
Compute & orchestration
| Service | Role |
|---|---|
| AWS EKS | Managed Kubernetes control plane — hosts all three app services |
| EKS node group (t3.small spot) | Single node group, 1–3 nodes, spot pricing for cost savings |
| Frontend deployment | Next.js 14 served via Nginx, HPA enabled |
| API deployment | Fastify REST API, HPA min 1 max 5 at 70% CPU |
| Worker deployment | BullMQ consumer, processes click jobs async |
Networking & ingress
| Service | Role |
|---|---|
| AWS ALB | Internet-facing load balancer, terminates HTTPS |
| AWS Load Balancer Controller | Provisions ALB from Kubernetes Ingress resources |
| cert-manager + Let’s Encrypt | Automatic TLS certificate provisioning and renewal |
| Route 53 | DNS — CNAME pointing domain to ALB hostname |
Data & storage
| Service | Role |
|---|---|
| PostgreSQL (Bitnami Helm) | Primary database — urls and clicks tables, 10Gi PVC |
| Redis (Bitnami Helm) | URL cache (24hr TTL per slug) + BullMQ job queue |
| ECR | Three repositories — api, worker, frontend — images tagged by Git SHA |
Async processing
| Service | Role |
|---|---|
| BullMQ | Job queue backed by Redis — fires click tracking job on every redirect |
| Worker (Node.js) | Consumes BullMQ jobs, parses geo from IP (geoip-lite), detects device type, writes to Postgres |
Observability
| Service | Role |
|---|---|
| Prometheus + Pushgateway | Metrics from all three services — redirect latency, click rate, error rate |
| Grafana | Dashboards stored as JSON in repo, applied via Terraform Grafana provider |
| Fluent Bit | Ships container logs to CloudWatch |
CI/CD pipeline
| Service | Role |
|---|---|
| GitHub Actions | Two workflows — deploy.yml (push to main) and pr-check.yml (PRs) |
| Docker build matrix | Builds api, worker, frontend images in parallel |
| ECR push | Images tagged with $GITHUB_SHA — every deploy is traceable to a commit |
kubectl set image | Rolling update — zero downtime deploy on every push to main |
Key design decisions
302 redirects, not 301 — 302 (temporary) redirects are used so browsers don’t cache them permanently. Every click hits the API, ensuring accurate analytics. 301 redirects would bypass the server on repeat visits and break click counts.
Redis-first cache strategy — every redirect checks Redis before hitting Postgres. Cache hit latency is ~1ms vs ~10ms for a DB query. At 90%+ cache hit rate, the API stays fast under load with minimal database pressure.
Async click tracking with BullMQ — the redirect response is sent immediately. Click tracking (geo lookup, device parsing, DB write) happens in a background worker. This keeps P99 redirect latency under 50ms regardless of database load.
nanoid for slug generation — 6-character URL-safe slugs give ~68 billion combinations. Cryptographically random, no collision risk at portfolio scale.
HPA on the API — the API deployment scales automatically between 1 and 5 replicas at 70% CPU. Demonstrates Kubernetes-native scaling in the demo.
Dashboards as code — Grafana dashboards are stored as JSON in /grafana/dashboards/ and applied via the Terraform Grafana provider. No manual dashboard configuration — everything is reproducible from the repo.
Project structure
url-shortener/
├── .github/workflows/ # deploy.yml + pr-check.yml
├── frontend/ # Next.js 14 app + Dockerfile
├── api/ # Fastify API + routes + services + Dockerfile
├── worker/ # BullMQ worker + Dockerfile
├── k8s/base/ # Deployment, Service, HPA, Ingress manifests
├── k8s/overlays/ # dev + prod environment patches
├── docs/ # architecture.md, data-model.md, api-spec.md, runbook.md
├── scripts/ # setup-eks.sh, setup-ecr.sh, local-seed.sh
└── docker-compose.yml # full local dev environment