This Product Requirements Document (PRD) outlines the requirements for building a URL Shortener microservice. The application allows users to shorten long URLs into compact, shareable links, redirect to the original URL when accessed, and track basic analytics like click counts. Designed as a backend-focused project using Java Spring Boot, it serves as a portfolio piece. The goal is to create a simple, deployable API that demonstrates core backend skills, with instructions clear enough for a novice developer, LLM, or AI assistant to implement from scratch to production.
The URL Shortener is a lightweight web service inspired by tools like Bitly or TinyURL. It provides RESTful endpoints for creating short URLs, redirecting to originals, and viewing analytics. Key benefits include ease of implementation, scalability potential, and relevance in real-world applications (e.g., marketing, social media). Target users are developers testing APIs or end-users needing quick link shortening.
- Project Goals: Build a functional MVP in 1-2 weeks, deployable to a cloud platform like Heroku or Render.
- Assumptions: Users have basic Java knowledge; no frontend is required (use tools like Postman for testing).
- Success Metrics: 100% uptime for redirects, <1s response time, accurate click tracking.
- Timeline: Setup (1 day), Core Features (3-5 days), Testing/Deployment (2-3 days).
This PRD serves as a blueprint for developing the URL Shortener. It is structured to guide a new developer or AI assistant step-by-step, from environment setup to production deployment. The project emphasizes best practices in API design, database management, and containerization with Docker, making it educational and production-ready.
- Problem Statement: Long URLs are cumbersome; shortening them improves usability while allowing analytics.
- Target Audience: API consumers (e.g., via curl or web apps), developers learning backend Java.
- Out-of-Scope: Advanced features like custom domains, user accounts (beyond basic auth if added), or rate limiting (consider as extensions).
- In-Scope: Core shortening, redirection, click tracking with PostgreSQL storage; Docker for local dev; Basic API security.
- Out-of-Scope: Frontend UI, payment integration, advanced analytics (e.g., geo-tracking).
- Dependencies: Internet access for external APIs if extended; Free tiers of cloud services for deployment.
To keep it simple and modern, use the following stack. Instructions assume a Windows/Mac/Linux setup with Java installed.
- Backend Framework: Spring Boot 3.x (for REST APIs, easy setup with Spring Initializr).
- Language: Java 17+ (LTS version for stability).
- Database: PostgreSQL (for persistence of URLs and analytics; use H2 for in-memory testing).
- Containerization: Docker (for local dev and deployment; include Dockerfile and docker-compose.yml).
- Build Tool: Gradle (preferred over Maven for faster builds; use build.gradle.kts for Kotlin DSL).
- Testing: JUnit 5 (unit/integration tests), Postman or curl for API testing.
- Other Tools:
- Git for version control.
- IDE: IntelliJ IDEA Community Edition (free) or VS Code with Java extensions.
- Deployment: Heroku (free tier) or Render.com; Optionally, AWS/EC2 for prod.
 
- Dependencies (add to build.gradle.kts):
plugins { id("org.springframework.boot") version "3.2.0" id("io.spring.dependency-management") version "1.1.6" kotlin("jvm") version "1.9.20" } dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.postgresql:postgresql:42.7.3") implementation("org.springframework.boot:spring-boot-starter-test") { forTest() } implementation("org.projectlombok:lombok") annotationProcessor("org.projectlombok:lombok") // Optional: for Swagger/OpenAPI implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0") // Optional: for basic auth on analytics implementation("org.springframework.boot:spring-boot-starter-security") }
Setup Instructions:
- Install Java JDK 17+ (via sdkman or official site: https://adoptium.net/).
- Install Gradle (or use wrapper: ./gradlew).
- Install Docker Desktop (https://www.docker.com/products/docker-desktop).
- Use Spring Initializr (https://start.spring.io/) to generate project: Select Gradle (Kotlin DSL), Java, Spring Boot 3.x, add dependencies above.
- Clone repo or create new: git init.
- Set up PostgreSQL: Use Docker to run a local instance (docker run -p 5432:5432 -e POSTGRES_PASSWORD=password postgres).
- Configure application.properties:spring.datasource.url=jdbc:postgresql://localhost:5432/postgres spring.datasource.username=postgres spring.datasource.password=password spring.jpa.hibernate.ddl-auto=update server.port=8080 
- High-Level Design: Monolithic microservice with layers: Controller (API endpoints), Service (business logic), Repository (DB access), Entity (DB models).
- Data Flow:
- User POSTs long URL → Generate unique short code → Store in DB → Return short URL.
- User GETs short URL → Redirect to original → Increment click count.
 
- Database Schema:
- Table: urls
- id: BIGINT PRIMARY KEY AUTO_INCREMENT
- original_url: VARCHAR(2048) NOT NULL
- short_code: VARCHAR(10) UNIQUE NOT NULL (e.g., base62 encoded)
- click_count: INT DEFAULT 0
- created_at: TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 
 
- Table: urls
- Error Handling: Use @ControllerAdvice for global exceptions (e.g., 404 for invalid short codes).
- Security: HTTPS in prod; Optional JWT or basic auth for admin endpoints.
Core features to implement, prioritized for MVP.
- 
URL Shortening: - Endpoint: POST /api/shorten
- Request: JSON { "originalUrl": "https://example.com/long" }
- Response: JSON { "shortUrl": "http://localhost:8080/abc123" }
- Logic: Generate unique short code (e.g., using MurmurHash or base62 on ID).
 
- 
URL Redirection: - Endpoint: GET /{shortCode}
- Response: HTTP 302 Redirect to original URL; Increment click_count.
 
- 
Analytics Viewing: - Endpoint: GET /api/analytics/{shortCode}
- Response: JSON { "originalUrl": "...", "clicks": 5, "createdAt": "..." }
 
- 
Optional: Bulk Shortening (for extension): - POST /api/bulk-shorten with array of URLs.
 
Implementation Tips:
- Use @RestController for APIs.
- Entity: @Entity public class Url { ... }
- Repository: extends JpaRepository<Url, Long>
- Service: Generate short code: Convert ID to base62 (implement helper method).
- Example base62 generator:
private static final String ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; public String toBase62(long id) { StringBuilder sb = new StringBuilder(); while (id > 0) { sb.append(ALPHABET.charAt((int) (id % 62))); id /= 62; } return sb.reverse().toString(); } 
Requirements for quality and operations.
- Performance: <500ms for shortening/redirect; Handle 100 req/s (scale with caching later).
- Scalability: Design for horizontal scaling; Use Redis for caching if extended.
- Security: Validate URLs (prevent open redirects); Sanitize inputs; HTTPS in prod.
- Reliability: 99.9% uptime; Handle DB failures with retries.
- Usability: Clear API docs (use Swagger: access at /swagger-ui/index.html).
- Maintainability: Clean code, comments, 80% test coverage.
- Accessibility: N/A (API-only).
- Compliance: GDPR-friendly (no user data stored).
Written in Gherkin-style for clarity; Prioritize MVP stories.
- As a user, I want to shorten a URL so I can share compact links.
- Given a valid long URL, when I POST to /api/shorten, then I get a unique short URL.
 
- As a user, I want to access the original URL via the short link.
- Given a valid short code, when I GET /{shortCode}, then I'm redirected to the original.
 
- As an admin, I want to view click analytics for a short URL.
- Given a short code, when I GET /api/analytics/{shortCode}, then I see click count and details.
 
- As a developer, I want error handling for invalid inputs.
- Given an invalid URL, when shortening, then return 400 Bad Request with message.
 
- As a deployer, I want Docker support to run locally/prod.
- Given Dockerfile, when I build/run, then app starts with DB.
 
Detailed specs for implementation.
- POST /api/shorten
- Body: { "originalUrl": string }
- Response: 201 Created, { "shortUrl": string }
 
- GET /{shortCode}
- Response: 302 Found, Location: originalUrl
 
- GET /api/analytics/{shortCode}
- Response: 200 OK, { "originalUrl": string, "clickCount": int, "createdAt": datetime }
 
- Error Responses: Standard HTTP codes with JSON { "error": "message" }
Use @GetMapping, @PostMapping in controllers.
- Use JPA/Hibernate for auto-schema creation (spring.jpa.hibernate.ddl-auto=update).
- For prod, use Flyway for migrations (add dependency: implementation("org.flywaydb:flyway-core")).
- Initial Data: Seed with sample URLs via CommandLineRunner.
- Example Flyway migration (src/main/resources/db/migration/V1__init.sql):
CREATE TABLE urls ( id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, original_url VARCHAR(2048) NOT NULL, short_code VARCHAR(10) UNIQUE NOT NULL, click_count INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); 
- Unit Tests: Service layer (e.g., short code generation).
- Integration Tests: API endpoints with @SpringBootTest and MockMvc.
- E2E Tests: Use Postman collection (create and share JSON).
- Coverage: Aim for 80%; Use Gradle: ./gradlew jacocoTestReport.
- Instructions: Run ./gradlew test; Fix failures before deploy.
Step-by-step to production.
- Local Dev: ./gradlew bootRunor Docker: Build image, run with compose (link to Postgres container).- Example Dockerfile:
FROM openjdk:17-jdk-slim WORKDIR /app COPY build/libs/*.jar app.jar ENTRYPOINT ["java", "-jar", "app.jar"] 
- Example docker-compose.yml:
version: '3.8' services: app: build: . ports: - "8080:8080" depends_on: - db environment: - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/postgres - SPRING_DATASOURCE_USERNAME=postgres - SPRING_DATASOURCE_PASSWORD=password db: image: postgres:latest environment: - POSTGRES_PASSWORD=password ports: - "5432:5432" 
 
- Example Dockerfile:
- CI/CD: Use GitHub Actions (simple YAML: build, test, deploy to Heroku).
- Example workflow (.github/workflows/ci.yml):name: CI/CD on: push: branches: [ main ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up JDK 17 uses: actions/setup-java@v3 with: java-version: '17' - name: Build with Gradle run: ./gradlew build - name: Deploy to Heroku env: HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} run: ./gradlew deployHeroku 
 
- Example workflow (
- Prod Deployment:
- Heroku: Create app, add Postgres add-on, push via git (heroku git:remote -a app-name).
- Config: Set environment vars (SPRING_DATASOURCE_URL, etc.).
- Monitor: Heroku dashboard for logs/metrics.
 
- Heroku: Create app, add Postgres add-on, push via git (
- Domain: Use free subdomain or custom via Cloudflare.
- Scaling: Add New Relic or basic monitoring.
- Risk: Duplicate short codes → Mitigation: Use unique constraints in DB.
- Risk: High traffic → Mitigation: Add caching (Redis).
- Risk: Security vulnerabilities → Mitigation: Regular dependency scans (./gradlew dependencies).
- User authentication for private links.
- Custom short codes.
- Geo-analytics.
- Integration with external APIs (e.g., virus scanning URLs).
- Frontend (React/Vue) for UI.
- References: Spring Boot docs (https://spring.io/projects/spring-boot), Gradle docs (https://gradle.org/), PostgreSQL tutorial.