Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ”— URL Shortener β€” System Design Study Project

A full-stack URL shortener built to learn and practice system design concepts hands-on. The project implements core distributed systems patterns β€” URL shortening with Base62 encoding, rate limiting with a sliding window algorithm, and round-robin load balancing β€” all in a clean Spring Boot + React stack.


Architecture Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                              BROWSER                                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”              β”‚
β”‚  β”‚  React Frontend (localhost:3000)                          β”‚              β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚              β”‚
β”‚  β”‚  β”‚ ShorternForm β”‚  β”‚ ResultCard β”‚  β”‚   LinkHistory     β”‚ β”‚              β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚              β”‚
β”‚  β”‚         β”‚              β–²                    β–²             β”‚              β”‚
β”‚  β”‚         β–Ό              β”‚                    β”‚             β”‚              β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜             β”‚              β”‚
β”‚  β”‚  β”‚            urlService.js (axios)                       β”‚              β”‚
β”‚  β”‚  β”‚   POST /api/urls/shorten  GET /api/urls                β”‚              β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β”‚
β”‚                            β”‚  CORS                                          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                             β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Spring Boot Backend (localhost:8080)                                        β”‚
β”‚                            β”‚                                                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚                    RateLimiterInterceptor                              β”‚ β”‚
β”‚  β”‚           (Sliding Window β€” 5 req/min per IP)                         β”‚ β”‚
β”‚  β”‚             Returns HTTP 429 if limit exceeded                        β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                            β”‚                                                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚                      UrlController                                    β”‚ β”‚
β”‚  β”‚  POST /api/urls/shorten  β”‚  GET /api/urls  β”‚  GET /{shortId} (302)    β”‚ β”‚
β”‚  β”‚  DELETE /api/urls/{id}   β”‚                 β”‚                          β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                            β”‚                                                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚                       UrlService                                      β”‚ β”‚
β”‚  β”‚  shortenUrl()  β”‚  getAllUrls()  β”‚  getOriginalAndTrackClick()          β”‚ β”‚
β”‚  β”‚                β”‚                β”‚  (increments clickCount)             β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚           β”‚                                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                β”‚
β”‚  β”‚  Base62Util       β”‚   β”‚  UrlRepository  β”‚                                β”‚
β”‚  β”‚  (ID β†’ shortCode) β”‚   β”‚  (JPA/SQLite)   β”‚                                β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                β”‚
β”‚                                  β”‚                                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚                     SQLite (urlshortner.db)                            β”‚ β”‚
β”‚  β”‚  url_mapping: id | long_url | short_id | click_count | created_at     β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Tech Stack

Layer Technology Purpose
Frontend React 19, Axios, Tailwind CSS SPA for shortening URLs and viewing history
Backend Spring Boot 4.0, Java 17 REST API, business logic, CORS handling
Database SQLite (via JPA/Hibernate) Persistent storage for URL mappings
Encoding Base62 (custom utility) Converts auto-increment DB IDs to short codes

System Design Concepts Implemented

1. URL Shortening (Base62 Encoding)

The core algorithm converts a database auto-increment ID into a short alphanumeric string:

ID = 1  β†’  encode(1)  β†’  "b"
ID = 62 β†’  encode(62) β†’  "ba"
ID = 238328 β†’ encode(238328) β†’ "bzzz"

Why Base62? β€” Uses a-z, A-Z, 0-9 (62 chars). A 7-character code supports 62⁷ = 3.5 trillion unique URLs. No special characters, so codes are URL-safe and copy-paste friendly.

Custom alias support β€” Users can optionally provide their own alias (e.g., my-site) instead of the auto-generated code.

2. Rate Limiting (Sliding Window)

Every incoming request passes through a RateLimiterInterceptor before reaching any controller:

Request β†’ [RateLimiterInterceptor] β†’ Allow? β†’ Controller
                    β”‚
                    β”œβ”€ Tracks timestamps per IP in a ConcurrentHashMap
                    β”œβ”€ Window: 60 seconds, Limit: 5 requests
                    └─ Exceeds? β†’ HTTP 429 "Too Many Requests"

Algorithm: Sliding Window Log β€” stores exact request timestamps and removes expired ones on each check. Thread-safe via ConcurrentHashMap + Collections.synchronizedList.

3. Load Balancer (Round-Robin)

A simple round-robin load balancer distributes requests across multiple backend instances:

Request 1 β†’ localhost:8081
Request 2 β†’ localhost:8082
Request 3 β†’ localhost:8083
Request 4 β†’ localhost:8081  (cycles back)

Uses AtomicInteger for thread-safe index rotation.

4. CORS (Cross-Origin Resource Sharing)

Configured in CorsConfig.java to allow the React dev server (port 3000) to call the Spring Boot API (port 8080):

registry.addMapping("/**")
        .allowedOrigins("*")
        .allowedMethods("*");

API Endpoints

Method Endpoint Description Request Body Response
POST /api/urls/shorten Shorten a URL {"originalUrl": "...", "alias": "..."} UrlResponse JSON
GET /api/urls Get all shortened URLs β€” UrlResponse[]
DELETE /api/urls/{id} Delete a URL by ID β€” 204 No Content
GET /{shortId} Redirect to original URL β€” 302 Redirect

Response Shape (UrlResponse)

{
  "id": 1,
  "originalUrl": "https://google.com",
  "shortUrl": "http://localhost:8080/b",
  "shortCode": "b",
  "clickCount": 3,
  "createdAt": "2026-02-21T00:31:35.092"
}

Project Structure

SystemDesignURLShortner/
β”œβ”€β”€ URLShortner/                          # Spring Boot Backend
β”‚   └── src/main/java/com/systemdesig/
β”‚       β”œβ”€β”€ SystemDesignApplication.java  # Entry point
β”‚       β”œβ”€β”€ config/
β”‚       β”‚   β”œβ”€β”€ CorsConfig.java           # CORS policy
β”‚       β”‚   β”œβ”€β”€ WebConfig.java            # Interceptor registration
β”‚       β”‚   └── RateLimiterInterceptor.java # Pre-request rate check
β”‚       β”œβ”€β”€ urlshortner/
β”‚       β”‚   β”œβ”€β”€ UrlController.java        # REST endpoints
β”‚       β”‚   β”œβ”€β”€ UrlService.java           # Business logic
β”‚       β”‚   β”œβ”€β”€ UrlRepository.java        # JPA data access
β”‚       β”‚   β”œβ”€β”€ UrlMapping.java           # Entity (DB table)
β”‚       β”‚   β”œβ”€β”€ ShortenRequest.java       # Request DTO
β”‚       β”‚   └── UrlResponse.java          # Response DTO
β”‚       β”œβ”€β”€ ratelimiter/
β”‚       β”‚   β”œβ”€β”€ RateLimitController.java  # Rate limit test endpoint
β”‚       β”‚   └── RateLimiterService.java   # Sliding window algorithm
β”‚       β”œβ”€β”€ loadbalancer/
β”‚       β”‚   β”œβ”€β”€ LoadBalancerController.java
β”‚       β”‚   └── LoadBalancerService.java  # Round-robin selector
β”‚       └── common/util/
β”‚           └── Base62Util.java           # ID β†’ short code encoding
β”‚
└── url_frontend/                         # React Frontend
    └── src/
        β”œβ”€β”€ App.jsx                       # Main app with history + form
        β”œβ”€β”€ api/urlService.js             # Axios HTTP client
        └── components/
            β”œβ”€β”€ ShorternForm.jsx          # URL input + shorten button
            β”œβ”€β”€ ResultCard.jsx            # Displays shortened URL + copy
            β”œβ”€β”€ LinkHistory.jsx           # Table of past shortened URLs
            └── Navbar.jsx                # Top navigation bar

Getting Started

Prerequisites

  • Java 17+
  • Maven 3.8+
  • Node.js 18+

Run the Backend

cd URLShortner
mvn spring-boot:run
# Starts on http://localhost:8080

Run the Frontend

cd url_frontend
npm install
npm start
# Opens http://localhost:3000

Quick Test with curl

# Shorten a URL
curl -X POST http://localhost:8080/api/urls/shorten \
  -H "Content-Type: application/json" \
  -d '{"originalUrl":"https://github.com"}'

# Get history
curl http://localhost:8080/api/urls

# Test redirect (use the shortCode from the shorten response)
curl -v http://localhost:8080/b

Future Improvements Roadmap

Phase 1 β€” Production Database

Change Why
Replace SQLite β†’ PostgreSQL SQLite doesn't support concurrent writes well; PSQL handles production workloads
Add Flyway migrations Version-controlled schema changes instead of ddl-auto=update
Add connection pooling config HikariCP tuning for production connection management

Phase 2 β€” Redis for Rate Limiting & Caching

Change Why
Move rate limiter state to Redis In-memory ConcurrentHashMap is lost on restart and doesn't work across multiple server instances
Use Redis SORTED SET for sliding window ZADD + ZRANGEBYSCORE gives O(log n) sliding window with automatic expiry
Add Redis cache for hot short URLs Avoid DB lookups for frequently accessed short codes; huge latency reduction
TTL-based cache eviction Auto-expire cache entries for rarely used URLs

Phase 3 β€” Distributed System Patterns

Change Why
Distributed ID generation (Twitter Snowflake / UUID) Auto-increment IDs don't scale across multiple DB instances
Real load balancer (Nginx / Spring Cloud Gateway) Replace the demo round-robin class with infrastructure-level load balancing
Multiple backend instances Run 2-3 Spring Boot instances behind the load balancer
Database read replicas Separate read traffic (redirects) from write traffic (shortening)

Phase 4 β€” Analytics & Monitoring

Change Why
Click analytics dashboard Track clicks by time, geography, referrer
Store click events in a time-series DB (InfluxDB / TimescaleDB) Efficient storage for high-volume event data
Add Prometheus + Grafana monitoring Track API latency, error rates, throughput
Structured logging (ELK stack) Centralized log collection and searchability

Phase 5 β€” Security & Reliability

Change Why
User authentication (JWT / OAuth2) Associate URLs with user accounts
URL validation & sanitization Block malicious URLs, validate format
Custom domain support Let users use their own domain for short links
Expiry / TTL for short URLs Auto-delete URLs after a configurable period
Restrict CORS to specific origins allowedOrigins("*") is fine for dev, not for production

System Design Diagram (Scaled Version)

                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  Client  β”‚
                    β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
                         β”‚
                    β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”
                    β”‚  Nginx   β”‚  (Load Balancer + SSL Termination)
                    β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
                         β”‚
            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
            β–Ό            β–Ό            β–Ό
      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
      β”‚ App Srv 1β”‚ β”‚ App Srv 2β”‚ β”‚ App Srv 3β”‚  (Spring Boot instances)
      β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
           β”‚             β”‚            β”‚
           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                         β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β–Ό                     β–Ό
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚  Redis   β”‚         β”‚ Postgres β”‚
        β”‚ (Cache + β”‚         β”‚ (Primary β”‚
        β”‚  Rate    β”‚         β”‚  + Read  β”‚
        β”‚  Limit)  β”‚         β”‚ Replicas)β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Built for learning system design β€” one component at a time. πŸš€

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages