Skip to content

Repository files navigation

URL Shortener with Analytics

A URL shortener with per-click analytics, built with Java 24, Spring Boot 4, PostgreSQL, and Redis.

  • Base62 short codes derived from the database primary key (plus a 62³ offset) — collision-free by construction, no random generation or retry loops.
  • Redis cache-aside layer over redirect lookups (10-minute TTL) — hot links resolve without touching PostgreSQL.
  • Click analytics captured on every redirect (timestamp, referrer, user-agent) and exposed as aggregate metrics (total clicks, clicks per day, top referrers) through a stats API.

📖 New to Spring Boot? TUTORIAL.md walks through this entire codebase lesson by lesson, written for developers coming from Node + Express.

API

Method Path Description
POST /api/urls Create a short link. Body: {"url": "https://..."}201 + link details
GET /{shortCode} Redirect (302) to the original URL; records a click
GET /api/urls/{shortCode} Link details
GET /api/urls/{shortCode}/stats Aggregate analytics for the link
GET /health Liveness check

Errors follow RFC 7807 (application/problem+json); validation failures include a per-field error map.

Example

curl -X POST http://localhost:8080/api/urls \
  -H "Content-Type: application/json" \
  -d '{"url":"https://github.com"}'
# {"shortCode":"1001","shortUrl":"http://localhost:8080/1001", ...}

curl -i http://localhost:8080/1001          # 302 → Location: https://github.com

curl http://localhost:8080/api/urls/1001/stats
# {"totalClicks":5,"clicksByDay":[{"day":"2026-07-18","clicks":5}],
#  "topReferrers":[{"referrer":"(direct)","clicks":2}, ...]}

Architecture

POST /api/urls              GET /{code}                     GET /api/urls/{code}/stats
     │                           │                                │
UrlController              RedirectController                UrlController
     │                           │                                │
UrlService                 1. Redis GET url:{code} ── hit ──► 302 UrlService.getStats
  INSERT urls                 miss ▼                              │
  code = Base62(62³+id)    2. SELECT urls → SET (TTL 10m)    GROUP BY day / referrer
     │                     3. INSERT clicks                       │
PostgreSQL (urls ←FK─ clicks)          Redis (url:{code})    PostgreSQL

Redirects use 302 (not 301) deliberately: a 301 is cached by browsers, so subsequent clicks would bypass the server and never be counted.

Running locally

Prerequisites: Java 17+, Docker.

docker compose up -d        # PostgreSQL :5432, Redis :6380
./mvnw spring-boot:run      # Windows: .\mvnw.cmd spring-boot:run

App starts on http://localhost:8080:

  • / — demo UI: shorten links and view analytics from the browser
  • /swagger-ui.html — interactive OpenAPI docs (try every endpoint live)

Configuration lives in src/main/resources/application.yml.

Project structure

src/main/java/com/abhiram/urlshortener/
├── controller/   UrlController, RedirectController, HealthController
├── service/      UrlService (create/resolve/stats + cache), ClickService
├── repository/   UrlRepository, ClickRepository (derived + native queries)
├── model/        Url, Click (JPA entities)
├── dto/          request/response records
├── exception/    UrlNotFoundException, GlobalExceptionHandler (RFC 7807)
└── util/         Base62

Deployment

The included multi-stage Dockerfile builds a slim production image. Configuration is environment-driven (SPRING_DATASOURCE_URL, SPRING_DATA_REDIS_URL, APP_BASE_URL, PORT), so it deploys cleanly to Render (app) + Neon (Postgres) + Upstash (Redis) — see TUTORIAL.md Lesson 10 for the step-by-step.

Possible extensions

Async click recording, negative caching for unknown codes, Flyway migrations, custom aliases, link expiry, rate limiting, Testcontainers-based integration tests.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages