A modern URL shortening service built with Spring Boot and PostgreSQL, featuring OAuth authentication, rate limiting, and comprehensive URL management capabilities.
- Overview
- Key Features
- Technology Stack
- Architecture
- Prerequisites
- Getting Started
- Project Structure
- Database Management
- Performance Optimization
- Contributing
- License
LinkShade is a production-ready URL shortening service designed with scalability, security, and user experience in mind. It provides both public and authenticated access, with robust rate limiting, OAuth integration, and comprehensive URL management features.
- Simplify URL Sharing: Convert long URLs into short, manageable links
- Track Engagement: Monitor clicks
- Secure Access Control: Support for both public and private URLs with user authentication
- Multi-tenant Support: User-specific URL management with admin capabilities
- Enterprise-Ready: Built with production best practices including rate limiting, validation, and error handling
- β URL Shortening: Generate short, unique identifiers for long URLs
- β Custom Short Codes: Create personalized short URL identifiers
- β Click Tracking: Monitor usage statistics for each shortened URL
- β URL Expiration: Automatic expiration with configurable time periods
- β Bulk Operations: Delete or reactivate multiple URLs at once
- β User Profiles: Manage URLs fields
- β Admin Dashboard: Administrative interface for user and URL management
- β OAuth 2.0 Integration: Login with Google and GitHub
- β Rate Limiting: Separate limits for anonymous and authenticated users
- β Private URLs: User-specific URL access control
- β Input Validation: Comprehensive validation for URLs and user input
- β Responsive Design: Bootstrap 5-based modern interface
- β Interactive Carousel: Product feature showcase
- β Pagination & Sorting: Efficient data browsing with customizable views
- β Error Pages: Custom error handling with user-friendly messages
- Java 21: Latest LTS version with modern language features
- Spring Boot 3.5.7: Enterprise-grade application framework
- Spring Security: OAuth 2.0 and authentication/authorization
- Spring Data JPA: Database abstraction and ORM
- Hibernate: JPA implementation for entity management
- Gradle: Dependency management and build automation
- PostgreSQL: Primary relational database
- Flyway: Database version control and migration management
- Thymeleaf: Server-side template engine
- Bootstrap 5.3.5: Responsive UI framework
- Bootstrap Icons 1.11.3: Icon library
- JavaScript: Client-side interactivity
- Docker: Containerization for consistent environments
- Docker Compose: Multi-container orchestration
- Lombok: Boilerplate code reduction
The application follows a layered architecture with clear separation of concerns:
βββββββββββββββββββββββββββββββββββββββββββ
β Presentation Layer β
β (Controllers, Templates, Interceptors) β
βββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββ
β Service Layer β
β (Business Logic, Validation, Mapping) β
βββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββ
β Repository Layer β
β (Data Access, JPA Repositories) β
βββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββ
β Database Layer β
β (PostgreSQL) β
βββββββββββββββββββββββββββββββββββββββββββ
The system is built around two primary domain entities:
User: Manages authenticated user information, OAuth provider data, and relationships to shortened URLsShortUrl: Stores shortened URLs with metadata including clicks, expiration dates, and privacy settings
Before running the application, ensure you have the following installed:
| Tool | Version | Purpose |
|---|---|---|
| Java JDK | 21+ | Required for running the application |
| Gradle | 8.12+ | Build and dependency management (or use included Gradle Wrapper) |
| Docker | Latest | For containerized PostgreSQL and application deployment |
| Docker Compose | Latest | For multi-container orchestration |
| Git | Latest | Version control |
Get up and running in 5 minutes (using docker compose):
# Clone the repository
git clone https://github.com/yourusername/linkshade.git
cd linkshade
# Set up environment variables (optional - see table below)
cp .env.example .env.local| Variable | Description | Required | Default | Example |
|---|---|---|---|---|
GITHUB_CLIENT_ID |
GitHub OAuth client ID | No* | - | abc123def456 |
GITHUB_CLIENT_SECRET |
GitHub OAuth client secret | No* | - | xyz789uvw456 |
GOOGLE_CLIENT_ID |
Google OAuth client ID | No* | - | 123456.apps.googleusercontent.com |
GOOGLE_CLIENT_SECRET |
Google OAuth client secret | No* | - | GOCSPX-abc123 |
SPRING_DATASOURCE_URL |
PostgreSQL JDBC URL | Yes | - | jdbc:postgresql://localhost:5432/linkshade-db |
SPRING_DATASOURCE_USERNAME |
Database username | Yes | - | postgres |
SPRING_DATASOURCE_PASSWORD |
Database password | Yes | - | postgres |
* Required for OAuth login functionality. Application will work without OAuth but users won't be able to log in.
Note
If you don't mind having this feature, skip this part and continue with running the app from here
To enable OAuth authentication, you'll need:
- Google OAuth 2.0 Credentials: Google Cloud Console
- GitHub OAuth App: GitHub Developer Settings
- Go to Google Cloud Console
- Create a new project or select an existing one
- Enable the Google+ API
- Create OAuth 2.0 credentials:
- Application type: Web application
- Authorized redirect URIs:
http://localhost:8080/login/oauth2/code/google
- Copy the Client ID and Client Secret to your
.env.localfile
- Go to GitHub Developer Settings
- Click "New OAuth App"
- Fill in the application details:
- Application name: LinkShade
- Homepage URL:
http://localhost:8080 - Authorization callback URL:
http://localhost:8080/login/oauth2/code/github
- Copy the Client ID and Client Secret to your
.env.localfile
Run the database in Docker and the application from your IDE or Gradle:
docker compose up -d linkshade-dbNote
You need to add the env.local path to the run configuration, otherwise
env variables won't be loaded and app won't be able to connect to the database
Run LinkshadeApplication.java or
./gradlew bootRunOpen your browser and navigate to:
http://localhost:8080
docker compose downTo remove all data:
docker compose down -vRun both the database and application as Docker containers:
docker compose up -d --buildTip
Omit --build flag if no code changes were made since the last build.
docker compose psExpected output:
NAME IMAGE STATUS PORTS
linkshade-app linkshade-app Up 0.0.0.0:8080->8080/tcp
linkshade-db postgres Up (healthy) 0.0.0.0:5432->5432/tcp
All services:
docker compose logs -fSpecific service:
docker compose logs -f [NAME]http://localhost:8080
docker compose downTo remove all data (including database volumes):
docker compose down -vlinkshade/
βββ src/
β βββ main/
β β βββ java/de/linkshade/
β β β βββ config/ # Application configuration
β β β βββ domain/ # Domain entities and DTOs
β β β β βββ entities/ # JPA entities
β β β β βββ dto/ # Data Transfer Objects
β β β βββ exceptions/ # Custom exceptions and handlers
β β β βββ repositories/ # Spring Data JPA repositories
β β β βββ security/ # Security configuration and OAuth
β β β βββ services/ # Business logic layer
β β β βββ utils/ # Utility classes
β β β βββ validation/ # Custom validators
β β β βββ web/ # Controllers and interceptors
β β βββ resources/
β β βββ db/migration/ # Flyway migration scripts
β β βββ static/ # Static assets (CSS, JS, images)
β β βββ templates/ # Thymeleaf templates
β β βββ application.yml # Main configuration
β β βββ application-prod.yml # Production configuration
β βββ test/ # Test files
βββ docker-compose.yml # Docker Compose configuration
βββ Dockerfile # Multi-stage Docker build
βββ build.gradle # Gradle build configuration
βββ settings.gradle # Gradle settings
βββ .env.example # Environment variables template
βββ README.md # This file
LinkShade uses Flyway for database version control and migration management. This ensures:
- Version Control: Track all database schema changes
- Consistency: Apply migrations uniformly across all environments
- Rollback Safety: Maintain migration history for audit trails
- Team Collaboration: Avoid schema conflicts
Flyway migrations are located in src/main/resources/db/migration/:
db/migration/
βββ V1__create_schema.sql # Initial schema creation
βββ V2__insert_data.sql # Seed data
βββ V{n}__{description}.sql # Subsequent migrations
- Format:
V{version}__{description}.sql - Example:
V3__add_user_roles.sql - Rules:
- Version must be numeric and unique
- Double underscore separates version from description
- Description uses underscores for spaces
The application implements best practices to avoid the N+1 query problem:
spring:
jpa:
open-in-view: falseBenefits:
- β
Closes
EntityManagerat transaction boundary - β Prevents lazy loading outside transaction context
- β Forces explicit data fetching strategies
- β Releases database connections promptly
- β Reduces database load
Implementation Strategy:
- Lazy Loading by Default: All relationships are marked with
FetchType.LAZY - Explicit Fetching: Use
@EntityGraphor JPQL JOIN FETCH for related data - Single Query Execution: Ensures only one query per operation instead of N+1
Example:
@EntityGraph(attributePaths = {"user"})
@Query("SELECT u FROM ShortUrl u WHERE u.id = :id")
Optional<ShortUrl> findByIdWithUser(@Param("id") Long id);Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Comprehensive unit test coverage
- Integration test suite
- QR code generation for shortened URLs
This project is licensed under the MIT License - see the LICENSE file for details.
Made with β€οΈ using Spring Boot