LinkForge is a full-featured URL shortening and link management platform built with Java 21 and Spring Boot 3. It's designed with a Headless Backend Architecture — providing the core API and infrastructure (Database + Caching) as a single bundled unit, ready for integration with any frontend.
Note
This is a Full Backend Infrastructure Bundle (REST API + PostgreSQL + Redis). The Frontend (React + TypeScript) is managed in a separate repository: linkforge-frontend
- Key Features
- Architecture
- Database Schema
- Tech Stack
- API Overview
- Getting Started
- Environment Variables
- Deployment
- Usage Guide
- Authors
- License
Anyone (including unauthenticated users) can shorten a URL. Each short link is assigned a unique code via TSID (Time-Sorted Unique Identifier). Links can optionally have:
- A custom alias instead of the auto-generated code.
- An expiration date, after which the link becomes inactive.
- A delete token returned upon creation so the link owner can remove it later without needing an account.
Registered users get their own dashboard to manage all their created links, including:
- Paginated, sortable, searchable list of links with full metadata.
- Ability to delete their own links without a delete token.
- QR Code generation for any of their links (VIP/Admin only).
VIP users and Admins can generate a QR code for any link they own. The QR code is created server-side using ZXing, encoded as a Base64 PNG, and stored alongside the link record.
Detailed click tracking with real-time data collection:
- Geolocation: Identify visitor country and city via IP-to-location.
- Device Detection: Track device types (Mobile, Desktop, Tablet, etc.) using User-Agent parsing.
- Referrer Tracking: Analyze where your traffic is coming from.
- Time-series Data: Visualize click trends over time with aggregated statistics.
Full internationalization support for both English (en) and Vietnamese (vi):
- Localized API error messages and success responses.
- Multilingual email templates for registration and password reset.
- Automatic language detection based on the
Accept-Languageheader.
Non-VIP users are shown an interstitial ad page before being redirected to the destination URL. The flow works as follows:
- User visits
/r/{shortCode}. - Backend checks if the link owner is VIP — if yes, the user is redirected immediately (301).
- If not VIP, a temporary ad token is generated (stored in Redis with a 30-second TTL) and the user is redirected (302) to a frontend buffer page.
- The frontend shows the ad for 5 seconds, then calls
POST /api/v1/ads/verifywith the token. - Backend verifies the token has existed for at least 5 seconds, then returns the original URL.
Users can purchase VIP packages that unlock premium features (ad-free redirects, custom aliases, QR code generation, etc.). Payment is processed via VNPay:
POST /api/v1/payments/vip-upgradecreates a VNPay checkout URL for the selected package.GET /api/v1/payments/vnpay-returnhandles the return callback — verifies the transaction signature, updates the user's VIP status and expiration, and redirects to the frontend.
Available VIP packages are Monthly, Yearly, and Lifetime options.
Full authentication system with the following features:
- Registration with email verification via OTP (sent through SMTP).
- Login returns a JWT access token + refresh token.
- Refresh Token rotation — old tokens are invalidated when refreshed.
- Forgot/Reset Password flow using OTP verification.
- Role-Based Access Control:
USERandADMINroles with method-level@PreAuthorizeguards.
Admins have access to additional endpoints for managing the entire platform:
- User Management: List all users (paginated, searchable), toggle VIP status.
- Link Management: View, delete, and manage QR codes for any user's links.
- Spring Boot Actuator exposes
/actuator/health,/actuator/info, and/actuator/prometheusendpoints. - Prometheus metrics endpoint for integration with monitoring stacks (Grafana, etc.).
The project follows Clean Architecture (Hexagonal Architecture) with four clearly separated layers:
graph TD
Client[Client Browser / API Consumer] --> Presentation[Presentation Layer <br/><i>Controllers, Response Wrappers</i>]
Presentation --> Application[Application Layer <br/><i>Use Cases, DTOs, Handlers</i>]
Application --> Domain[Domain Layer <br/><i>Entities, Value Objects, Business Rules</i>]
subgraph Infrastructure[Infrastructure Layer]
Persistence[PostgreSQL <br/><i>Flyway, Partitioning</i>]
Caching[Redis + Caffeine <br/><i>L1/L2 Strategy</i>]
Security[Spring Security <br/><i>JWT, Auth</i>]
External[External Services]
end
Domain -.-> Persistence
Application -.-> Caching
Application -.-> Security
Application -.-> External
External --> VNPay[VNPay Payment Gateway]
External --> Email[SMTP Email Service]
External --> GeoIP[IP Geolocation API]
src/main/java/com/tlavu/linkforge/
├── domain/ # Core business logic — entities, value objects, repository interfaces, exceptions
├── application/ # Use cases (business orchestration), DTOs, port interfaces
├── infrastructure/ # External concerns — persistence (JPA), security (JWT), cache (Redis), email, metrics
└── presentation/ # REST controllers, request/response wrappers, global exception handling
- Domain Layer contains pure business entities (
ShortLink,User,PaymentTransaction,RefreshToken,VipPackage) and value objects (OriginalUrl,ShortCode) with no framework dependencies. - Application Layer defines use cases like
CreateShortLinkUseCase,AuthUseCase,HandlePaymentWebhookUseCase, etc. Each use case follows the single-responsibility principle. - Infrastructure Layer implements technical details:
- Multi-level Caching: L1 (Caffeine In-memory) + L2 (Redis) for high-performance redirection.
- Table Partitioning: PostgreSQL native monthly partitioning for the
short_linkstable to handle massive scale. - Security: JWT-based auth with refresh token rotation and role-based access control.
- Rate Limiting: IP-based protection powered by Redis Lua scripts.
- Observability: Prometheus metrics and structured JSON logging.
- Presentation Layer exposes the REST API with standardized
ApiResponse<T>wrappers and aGlobalExceptionHandler.
LinkForge uses PostgreSQL 16 with a highly optimized schema for read-heavy workloads.
erDiagram
USER ||--o{ SHORT_LINK : owns
USER ||--o{ PAYMENT_TRANSACTION : makes
USER ||--o{ REFRESH_TOKEN : has
SHORT_LINK ||--o{ CLICK_ANALYTICS : generates
USER {
bigint id PK
string username
string email
string password_hash
string role
boolean is_vip
timestamp vip_expiration
}
SHORT_LINK {
bigint id PK
string code UK
string original_url
timestamp created_at
timestamp expires_at
bigint click_count
string qr_code_base64
}
CLICK_ANALYTICS {
bigint id PK
string short_code FK
timestamp clicked_at
string ip_address
string country
string city
string device_type
string referrer
}
PAYMENT_TRANSACTION {
bigint id PK
bigint user_id FK
string order_code UK
integer amount
string package_code
string status
timestamp created_at
timestamp paid_at
}
REFRESH_TOKEN {
bigint id PK
string token UK
bigint user_id FK
timestamp expires_at
boolean revoked
}
- Table Partitioning: The
short_linkstable is natively partitioned by month (created_at) for massive scalability. - Indexes: Strategic B-Tree indexes on
code,user_id, andcreated_atfor sub-millisecond lookups.
| Category | Technology |
|---|---|
| Language | Java 21 |
| Framework | Spring Boot 3.5.10 |
| Security | Spring Security + JWT (jjwt 0.12.7) |
| Database | PostgreSQL 16 |
| Caching | Redis + Caffeine (L1/L2 Strategy) |
| DB Migration | Flyway |
| ORM | Spring Data JPA / Hibernate |
| ID Generation | Hypersistence TSID (Base62) |
| Object Mapping | MapStruct + Lombok |
| QR Code | Google ZXing |
| API Docs | SpringDoc OpenAPI 2.8 (Swagger UI) |
| Monitoring | Spring Actuator + Micrometer Prometheus |
| Spring Mail (Gmail / Brevo SMTP) + Thymeleaf | |
| Testing | JUnit 5, Spring Security Test, Testcontainers |
| Containerization | Docker (multi-stage build) + Docker Compose |
| Deployment | Render (backend), Vercel (frontend) |
Base path: /api/v1
| Group | Prefix | Description |
|---|---|---|
| Authentication | /auth |
Register, verify email, login, refresh token, logout, forgot/reset password, get profile |
| Short Links | /links |
Create, retrieve, and delete short links (public, token-based) |
| My Links | /me/links |
Authenticated user's link dashboard — list, delete, generate/delete QR codes |
| Redirection | /r/{shortCode} |
Redirect short codes to original URLs (with ad interstitial logic) |
| Advertisements | /ads |
Verify ad tokens after interstitial wait |
| Payments | /payments |
Create VNPay checkout URL, handle return callback |
| Admin - Users | /admin/users |
List users, toggle VIP status |
| Admin - User Links | /admin/users/{userId}/links |
View/delete/manage QR for any user's links |
Interactive API documentation is available at:
http://localhost:8080/swagger-ui/index.html
- Java 21+
- Maven 3.9+
- Docker & Docker Compose (for infrastructure services)
This is the fastest way to run the complete backend infrastructure. It will spin up PostgreSQL, Redis, and the Spring Boot application together as a single unit:
# 1. Clone the repository
git clone https://github.com/tlavu2004/linkforge-backend.git
cd linkforge-backend
# 2. Get environment files
# Option A: Create from example
cp .env.example .env
# Edit .env and fill in your JWT_SECRET_KEY and other required values.
# Option B: Download pre-configured files
# Download .env or .env.prod from "Backend" folder of this [Google Drive Link](https://drive.google.com/drive/folders/13TrE4vfyboF2OTSe6Yj7t_SanFauAyhV?usp=sharing)
# and save it to the project root.
# 3. Start everything
docker compose up -dThe app will be available at http://localhost:8080.
Run only PostgreSQL and Redis in Docker, and the application natively:
# 1. Start database and cache
docker compose up -d postgres redis
# 2. Get environment file
# Option A: cp .env.example .env && (Configure .env)
# Option B: Download from "Backend" folder of this [Google Drive Link](https://drive.google.com/drive/folders/13TrE4vfyboF2OTSe6Yj7t_SanFauAyhV?usp=sharing) to the project root.
# 3. Run the application
mvn spring-boot:runOn first startup, a default admin account is automatically seeded using the values from ADMIN_EMAIL and ADMIN_PASSWORD environment variables. You can use this account to access admin-only endpoints.
LinkForge provides utility scripts for infrastructure cleanup (located in src/main/resources/scripts):
clean-postgres.sh: Flushes all data from the PostgreSQL database (use with care!).clean-redis.sh: Clears all keys from the Redis instance.
Usage:
./src/main/resources/scripts/db/clean-postgres.sh ./.env
./src/main/resources/scripts/redis/clean-redis.sh ./.envYou have two ways to configure your environment:
- Manual: Copy
.env.exampleto.envand fill in the required values. - Download: Get pre-configured
.env(Dev) and.env.prod(Production) files from "Backend" folder of this Google Drive Link.
Important
Always ensure your .env file is in the root directory before running the application or Docker.
| Variable | Description |
|---|---|
JWT_SECRET_KEY |
Secret key for signing JWT tokens. Generate one with openssl rand -base64 64. |
DB_URL |
PostgreSQL JDBC URL (default: jdbc:postgresql://localhost:5432/linkforge) |
DB_USERNAME |
Database username (default: postgres) |
DB_PASSWORD |
Database password (default: password) |
| Variable | Description | Default |
|---|---|---|
SPRING_PROFILES_ACTIVE |
Active Spring profile | dev |
PORT |
Server port | 8080 |
JWT_EXPIRATION |
Access token TTL (ms) | 900000 (15 min) |
JWT_REFRESH_TOKEN_EXPIRATION |
Refresh token TTL (ms) | 604800000 (7 days) |
REDIS_HOST |
Redis server hostname | localhost |
REDIS_PORT |
Redis server port | 6379 |
FRONTEND_URL |
Frontend base URL (for CORS & redirects) | http://localhost:5173 |
ADMIN_EMAIL |
Default admin email | admin@linkforge.com |
ADMIN_PASSWORD |
Default admin password | admin123 |
| Variable | Description |
|---|---|
VNPAY_TMN_CODE |
Merchant terminal code from VNPay |
VNPAY_HASH_SECRET |
Secure hash key from VNPay |
VNPAY_URL |
VNPay payment gateway URL |
VNPAY_RETURN_URL |
Callback URL after payment (e.g. http://localhost:8080/api/v1/payments/vnpay-return) |
VNPAY_API_URL |
VNPay transaction query API |
| Variable | Description | Default |
|---|---|---|
IP_API_URL |
IP Geolocation API URL | https://ip-api.com/json/ |
| Variable | Description | Default |
|---|---|---|
MAIL_HOST |
SMTP server hostname | smtp.gmail.com |
MAIL_PORT |
SMTP port | 587 |
MAIL_USERNAME |
SMTP username/email | — |
MAIL_PASSWORD |
SMTP password or app password | — |
MAIL_FROM |
Sender email address | noreply@linkforge.com |
The project includes a render.yaml Blueprint and a multi-stage Dockerfile optimized for Render's free tier (512MB RAM):
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75.0", "-XX:ActiveProcessorCount=1", "-jar", "app.jar"]Set SPRING_PROFILES_ACTIVE=prod and configure all required environment variables in Render's dashboard.
The frontend repository includes a vercel.json for deployment to Vercel. See the frontend README for details.
- Register a new account with email verification (OTP sent to your inbox).
- Login to access your personal dashboard.
- Paste any long URL into the input field — no account required.
- Optionally set a custom alias and expiration date.
- Click Shorten — your new short link and delete token are displayed immediately.
- Navigate to My Links to view all your created links.
- Use search, sort, and pagination to find specific links.
- Delete links or generate QR codes (VIP/Admin) directly from the table.
- Open any link from your dashboard.
- Click the QR Code button to generate a scannable QR code.
- The QR code can be downloaded or regenerated at any time.
- Navigate to the VIP Upgrade page.
- Choose a package: Monthly, Yearly, or Lifetime.
- Complete payment through the VNPay gateway.
- Enjoy ad-free redirects, QR code generation, and premium features.
- Login with an Admin account.
- Navigate to Admin Dashboard to manage users and links.
- Toggle VIP status for any user, or view/delete any link on the platform.
| Student ID | Full Name | Github |
|---|---|---|
| 22120443 | Trương Lê Anh Vũ | tlavu2004 |
- Fork the repository.
- Create a feature branch (
git checkout -b feature/AmazingFeature). - Commit your changes (
git commit -m 'Add some AmazingFeature'). - Push to the branch (
git push origin feature/AmazingFeature). - Open a Pull Request.
This project is licensed under the MIT License.








