A scalable URL shortener built with a polyglot microservices architecture. Designed as an educational project to study system design concepts commonly asked in interviews.
┌──────────────┐
│ API Gateway │ (Nginx – port 8080)
└──────┬───────┘
┌──────────┬────────────┼──────────────┬────────────┐
▼ ▼ ▼ ▼ ▼
┌───────────┐ ┌────────┐ ┌─────────────┐ ┌──────────┐ ┌──────────┐
│URL Service│ │Redirect│ │ Analytics │ │ WS │ │ Client │
│Java/Spring│ │Service │ │ Service │ │ Service │ │ Next.js │
│ Boot │ │TS/Nest │ │Python/Django│ │ Node.js │ │ React │
│ :8081 │ │ :8082 │ │ :8083 │ │ :8084 │ │ :3000 │
└─────┬─────┘ └───┬────┘ └─────┬───────┘ └────┬─────┘ └──────────┘
│ │ │ │
▼ ▼ ▼ │
┌──────────┐ ┌──────────┐ ┌──────────┐ │
│PostgreSQL│ │ Redis │ │ MongoDB │ │
│ :5432 │ │ :6379 │ │ :27017 │ │
└──────────┘ └──────────┘ └──────────┘ │
│ │
┌─────────────┐ │
│ RabbitMQ │◄─────────────────┘
│ :5672 │ (consumes click events)
│ UI :15672 │
└─────────────┘
| Service | Stack | Port | Database | Responsibility |
|---|---|---|---|---|
| URL Service | Java 17 + Spring Boot 3 | 8081 | PostgreSQL | Create short URLs, validate input, random short code gen |
| Redirect Service | TypeScript + NestJS | 8082 | Redis (cache) + PostgreSQL (fallback) | Handle redirects, check cache, publish click events |
| Analytics Service | Python 3.12 + Django 5 | 8083 | MongoDB | Consume click events from queue, serve analytics & trending |
| WS Service | Node.js + Socket.IO | 8084 | — (in-memory) | Real-time WebSocket relay, broadcast click & trending events |
| API Gateway | Nginx | 8080 | — | Reverse proxy, rate limiting, routing, WebSocket upgrade |
| Client | Next.js 14 + React 18 | 3000 | — | Web UI with dark/light theme, live analytics dashboard |
| Component | Port | Purpose |
|---|---|---|
| PostgreSQL 16 | 5432 | Relational store for URL records |
| MongoDB 7 | 27017 | Document store for click analytics |
| Redis 7 | 6379 | Cache layer for fast redirects |
| RabbitMQ 3 | 5672 / 15672 | Message queue for async click events & WS relay |
- Docker & Docker Compose installed
docker-compose up --buildThis starts all infrastructure + all 5 services (URL, Redirect, Analytics, WS, Client) + the API gateway.
Create a short URL:
curl -X POST http://localhost:8080/api/v1/urls \
-H "Content-Type: application/json" \
-d '{"longUrl": "https://www.google.com/search?q=system+design"}'Redirect (use the short code from the response):
curl -v http://localhost:8080/abc123Get analytics:
curl http://localhost:8080/api/v1/urls/abc123/analytics- Client UI: http://localhost:3000
- API Gateway: http://localhost:8080
- WebSocket (Socket.IO): http://localhost:8080/socket.io/
- RabbitMQ Management: http://localhost:15672 (urlshortener / urlshortener)
- Spring Actuator: http://localhost:8081/actuator/health
cd benchmarks
npm install
npm run bench # Run all benchmarks
npm run bench:url # URL creation only
npm run bench:redirect # Redirect service only
npm run bench:analytics # Analytics + Trending
npm run bench:ws # WebSocket event delivery
npm run bench:gateway # End-to-end via Nginx- Minikube + kubectl installed
- Nginx Ingress controller enabled
# Start minikube
minikube start
# Enable ingress
minikube addons enable ingress
# Build images inside minikube's Docker daemon
eval $(minikube docker-env) # Linux/Mac
# For PowerShell: minikube docker-env | Invoke-Expression
docker build -t url-service:latest ./url-service
docker build -t redirect-service:latest ./redirect-service
docker build -t analytics-service:latest ./analytics-service
# Apply manifests
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/config.yaml
kubectl apply -f k8s/init-db-configmap.yaml
kubectl apply -f k8s/infrastructure.yaml
kubectl apply -f k8s/services.yaml
kubectl apply -f k8s/ingress.yaml
# Check status
kubectl get all -n url-shortenerCreate a short URL.
Request:
{
"longUrl": "https://example.com/very/long/path",
"customAlias": "mylink", // optional
"expiryDate": "2026-12-31T23:59:59" // optional
}Response (201):
{
"shortUrl": "http://localhost:8080/abc123",
"shortCode": "abc123",
"longUrl": "https://example.com/very/long/path",
"expiryDate": null,
"createdAt": "2026-02-28T12:00:00"
}Error codes: 400 (invalid URL), 409 (alias conflict)
Redirect to the original URL.
Status codes: 301 (redirect), 404 (not found), 410 (expired)
Get click analytics for a short code.
Response (200):
{
"shortCode": "abc123",
"totalClicks": 42,
"clicksByCountry": { "unknown": 42 },
"clicksByDate": { "2026-02-28": 42 },
"recentClicks": [...]
}This project demonstrates the following system design concepts:
- Microservices architecture with polyglot tech stacks
- Secure random short codes (7-char alphanumeric via SecureRandom)
- Cache-aside pattern (Redis as read-through cache)
- Async event processing (RabbitMQ pub/sub for analytics)
- API Gateway pattern (Nginx reverse proxy + rate limiting)
- Database per service pattern (PostgreSQL for writes, MongoDB for analytics)
- Health checks and readiness probes
- Container orchestration (Docker Compose + Kubernetes)
- Horizontal scaling (multiple replicas for read-heavy redirect service)
- TTL-based expiry with background cleanup jobs
- Circuit breaker mindset (graceful fallbacks when Redis/RabbitMQ are down)
- Real-time WebSocket relay (Socket.IO via RabbitMQ consumer)
- Dark/Light theme support with CSS variable-driven design tokens
- CI/CD pipeline with GitHub Actions for automated testing
url-shortener/
├── docker-compose.yml # Local development orchestration
├── init-db.sql # PostgreSQL schema init
├── api-gateway/
│ └── nginx.conf # Reverse proxy + WebSocket upgrade
├── url-service/ # Java 17 + Spring Boot 3
│ ├── Dockerfile
│ ├── build.gradle
│ └── src/
│ ├── main/java/.../ # Controllers, services, entities, DTOs
│ └── test/java/.../ # Unit & integration tests
├── redirect-service/ # TypeScript + NestJS 10
│ ├── Dockerfile
│ ├── package.json
│ └── src/
│ ├── redirect/ # Controller + Service
│ ├── redis/ # Redis client
│ ├── database/ # PostgreSQL client
│ ├── rabbitmq/ # Message publisher
│ └── __tests__/ # Unit tests
├── analytics-service/ # Python 3.12 + Django 5
│ ├── Dockerfile
│ ├── requirements.txt
│ └── analytics/
│ ├── views.py # Analytics & Trending API views
│ ├── consumer.py # RabbitMQ consumer
│ └── tests.py # Unit tests
├── ws-service/ # Node.js + Socket.IO
│ ├── Dockerfile
│ ├── package.json
│ └── src/
│ ├── index.js # HTTP + Socket.IO + RabbitMQ consumer
│ └── __tests__/ # Unit tests
├── client/ # Next.js 14 + React 18 + Tailwind
│ ├── Dockerfile
│ └── src/
│ ├── app/ # Pages (shorten, analytics, trending, docs, status)
│ ├── components/ # Navbar, Footer, BackgroundOrbs, ThemeProvider
│ └── lib/ # API client, WebSocket hook
├── benchmarks/ # Performance benchmark suite (Node.js)
│ ├── run-all.js # Combined runner
│ ├── bench-url-service.js
│ ├── bench-redirect-service.js
│ ├── bench-analytics-service.js
│ ├── bench-ws-service.js
│ └── bench-gateway.js
├── docs/ # Service documentation
│ ├── url-service.md
│ ├── redirect-service.md
│ ├── analytics-service.md
│ ├── ws-service.md
│ └── client.md
├── .github/workflows/ # CI pipeline
│ └── ci.yml # Test on push to main
└── k8s/ # Kubernetes manifests
├── namespace.yaml
├── config.yaml
├── infrastructure.yaml
├── services.yaml
└── ingress.yaml