Skip to content

Repository files navigation

URL Shortener

A robust URL shortening service built with Spring Boot 3.2.3, featuring Redis caching, PostgreSQL persistence, and comprehensive error handling.

Features

  • URL Shortening: Generate short codes for long URLs using Base62 encoding
  • Custom Short Codes: Allow users to specify custom short codes
  • Click Tracking: Track usage statistics for each shortened URL
  • URL Expiration: Automatic URL expiration after configurable days (default: 30)
  • Redis Caching: Improve performance with Redis caching for URL lookups
  • Collision Handling: Automatic retry logic for short code generation
  • Structured Logging: Comprehensive logging using SLF4J
  • Health Monitoring: Spring Boot Actuator endpoints for health checks and metrics
  • Docker Support: Complete Docker Compose setup for easy deployment

Architecture

┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│   Client    │────▶│   Controller │────▶│   Service   │
└─────────────┘     └──────────────┘     └──────┬──────┘
                                                │
                                    ┌───────────┼───────────┐
                                    ▼           ▼           ▼
                              ┌─────────┐  ┌─────────┐  ┌─────────┐
                              │  Redis  │  │PostgreSQL│  │  Logs  │
                              └─────────┘  └─────────┘  └─────────┘

Technology Stack

  • Java 17
  • Spring Boot 3.2.3
  • Gradle 8.5 (Groovy)
  • PostgreSQL 15
  • Redis 7
  • JUnit 5 + Mockito for testing

Quick Start

Using Docker Compose (Recommended)

docker-compose up

This will start:

  • PostgreSQL on port 5432
  • Redis on port 6379
  • Spring Boot application on port 8080

Local Development

  1. Prerequisites

    • Java 17
    • PostgreSQL 15
    • Redis 7
  2. Database Setup

    CREATE DATABASE urlshortener;
  3. Build the project

    ./gradlew build
  4. Run the application

    ./gradlew bootRun

API Documentation

Endpoints

1. Shorten URL

POST /api/shorten

Request Body:

{
  "url": "https://example.com/very-long-url",
  "customCode": "mylink"  // optional
}

Response (201 Created):

{
  "data": {
    "shortCode": "abc123",
    "originalUrl": "https://example.com/very-long-url"
  },
  "success": true
}

2. Redirect

GET /api/{shortCode}

Response (302 Found):

  • Redirects to the original URL via Location header

3. Get Statistics

GET /api/stats/{shortCode}

Response (200 OK):

{
  "data": {
    "shortCode": "abc123",
    "originalUrl": "https://example.com/very-long-url",
    "createdAt": "2026-03-15T10:00:00",
    "clickCount": 42
  },
  "success": true
}

4. Health Check

GET /actuator/health

Response (200 OK):

{
  "status": "UP"
}

5. Metrics

GET /actuator/metrics

Response (200 OK):

{
  "names": ["jvm.memory.used", "http.server.requests", ...]
}

Environment Variables

Variable Description Default
SERVER_PORT Application port 8080
DB_URL PostgreSQL JDBC URL jdbc:postgresql://localhost:5432/urlshortener
DB_USERNAME Database username postgres
DB_PASSWORD Database password postgres
REDIS_HOST Redis host localhost
REDIS_PORT Redis port 6379
URL_EXPIRATION_DAYS URL expiration in days 30
JPA_SHOW_SQL Show SQL queries in logs true

Design Decisions

Why Base62 Encoding?

  • Uses alphanumeric characters (0-9, A-Z, a-z)
  • Provides 62^6 ≈ 56 billion possible combinations for 6-character codes
  • URL-safe and human-readable

Why Redis Caching?

  • Reduces database load for frequently accessed URLs
  • Improves response time for redirects
  • Simple key-value storage perfect for URL lookups

Why 302 vs 301 Redirect?

  • 302 (Found) is temporary - allows analytics tracking
  • 301 (Moved Permanently) would be cached by browsers, preventing accurate click counting
  • 302 is more appropriate for a URL shortener service

Why @Transactional?

  • Ensures atomicity when incrementing click counts
  • Prevents race conditions during concurrent redirects
  • Guarantees data consistency

Testing

Run unit tests:

./gradlew test

Run with coverage:

./gradlew test jacocoTestReport

Project Structure

src/
├── main/
│   ├── java/com/urlshortener/
│   │   ├── controller/      # REST controllers
│   │   ├── dto/             # Data Transfer Objects
│   │   ├── entity/          # JPA entities
│   │   ├── exception/       # Custom exceptions
│   │   ├── repository/      # JPA repositories
│   │   └── service/         # Business logic
│   └── resources/
│       └── application.properties
└── test/
    └── java/com/urlshortener/
        └── service/         # Unit tests

Error Handling

The API returns structured error responses:

{
  "error": "Short code not found: invalid",
  "status": 404,
  "timestamp": "2026-03-15T10:00:00"
}

HTTP Status Codes:

  • 201 - URL shortened successfully
  • 302 - Redirect to original URL
  • 400 - Invalid request (validation error)
  • 404 - Short code not found
  • 409 - Custom code already taken
  • 410 - URL has expired
  • 503 - Service unavailable (short code generation failure)
  • 500 - Internal server error

License

This project is for educational purposes.

About

A robust URL shortening service built with Spring Boot 3.2.3, featuring Redis caching, PostgreSQL persistence, and comprehensive error handling.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages