Butly is a URL shortener service. "Butly" is slang for short, representing compact URLs.
This project is optimized for Docker + VS Code Dev Containers.
👉 See CONTRIBUTING.md for full setup instructions.
- Tech Stack
- Features
- Architecture
- Getting Started
- Run with Docker
- API
- Redis Key Schema
- Environment Variables
- Notes
- License
| Component | Technology |
|---|---|
| Language | Java 25 |
| Framework | Spring Boot 4.x |
| Database | PostgreSQL |
| Cache | Redis |
| ORM | JPA (Hibernate) |
| Containerization | Docker / Docker Compose |
- Shorten long URLs into compact, shareable links
- Redirect from a short URL to the original long URL
- Base62-based ID encoding for short, URL-safe codes
- Redis caching for low-latency redirects
- PostgreSQL persistence as the source of truth
Client → Spring Boot → Redis → PostgreSQL
- Redis — fast path for redirects (cache layer)
- PostgreSQL — source of truth for all shortened URLs
Flow:
- Client requests a short URL for a long URL → persisted in PostgreSQL → cached in Redis.
- Client hits the short URL → service checks Redis first → falls back to PostgreSQL on a cache miss → repopulates Redis.
- Java 25
- Docker & Docker Compose
- Maven (or use the included wrapper
./mvnw)
docker run -p 8080:8080 ghcr.io/samsterzero/butly:latestThe service will be available at http://localhost:8080 (adjust if you've changed the port).
services:
app:
image: ghcr.io/samsterzero/butly:latest
ports:
- "8080:8080"
depends_on:
- postgres
- redis
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/butly
SPRING_DATASOURCE_USERNAME: butly
SPRING_DATASOURCE_PASSWORD: change_me
SPRING_REDIS_HOST: redis
SPRING_REDIS_PORT: 6379
postgres:
image: postgres:18
environment:
POSTGRES_DB: butly
POSTGRES_USER: butly
POSTGRES_PASSWORD: change_me
redis:
image: redis:8-alpineRun:
docker compose up -d# Start only PostgreSQL and Redis via Docker
docker-compose up -d postgres redis
# Run the app
./mvnw spring-boot:runPOST /api/urls
Request
{
"longUrl": "https://example.com"
}Response — 201 Created
{
"shortUrl": "abc123xy"
}Error Response — 400 Bad Request
{
"error": "Invalid URL format"
}GET /{shortCode}
Behavior:
- Checks Redis for
shortUrl:{code}. - On a cache miss, falls back to PostgreSQL and repopulates Redis.
- Returns a
302 Foundredirect to the original long URL.
Example
curl -i http://localhost:8080/abc123xyResponse
HTTP/1.1 302 Found
Location: https://example.com
Error Response — 404 Not Found (unknown short code)
{
"error": "Short URL not found"
}Key: shortUrl:{code}
Value: {longUrl}
TTL: 24 hours
After the TTL expires, the entry is evicted from Redis but remains permanently in PostgreSQL. The next redirect request for that code will repopulate the cache.
Create a .env.docker file (see .env.docker.example for a template — never commit real credentials):
POSTGRES_USER=butly
POSTGRES_PASSWORD=change_me
POSTGRES_DB=butly
POSTGRES_PORT=5432
REDIS_PORT=6379
APP_PORT=8080
⚠️ Security note: Always use a.env.docker.examplefile with placeholder values for version control, and keep your real.env.dockerin.gitignore.
- Base62 encoding is used to generate short, URL-safe codes.
- The database-generated ID is the source of uniqueness for each short code.
- Redis exists purely for performance — PostgreSQL remains authoritative if the cache is ever cleared or unavailable.
This project is licensed under the MIT License.