This project is a production-style URL shortener service written in Go. It demonstrates layered architecture, dependency injection, Redis caching, Redis-based rate limiting, and PostgreSQL persistence using GORM.
- Handler layer (
internal/handler): Gin HTTP handlers, request/response mapping and validation. - Service layer (
internal/service): Business logic, short code generation, caching, rate limiting, analytics. - Repository layer (
internal/repository): GORM-based data access for theurlstable. - Cache layer (
internal/cache): Redis-backed cache forshort_code -> original_url. - Rate limiter (
internal/ratelimiter): Redis-backed fixed-window rate limiter. - Models (
internal/models): GORM models for persistence. - Utilities (
pkg/base62): Base62 encoder for generating short codes from numeric IDs.
Request flow:
Client → Gin Handler → Service → Redis cache lookup → DB fallback → Response
Table: urls
- id (
uint64, primary key, auto-increment) - short_code (unique index)
- original_url
- click_count
- created_at
- expires_at
- last_accessed
cmd/server/main.go performs GORM AutoMigrate on startup to create/update the table.
-
POST
/shortenRequest body:
{ "url": "https://example.com/very-long-url", "custom_alias": "optional", "expires_in": "optional duration, e.g. \"24h\"" }Response:
{ "short_code": "abc123", "short_url": "http://localhost:8080/abc123", "expires_at": "2026-03-07T12:00:00Z" } -
GET
/:short_codeRedirects (302) to the original URL. Uses Redis cache first, then DB. Updates
click_countandlast_accessed. -
GET
/stats/:short_codeReturns analytics:
{ "short_code": "abc123", "original_url": "https://example.com/very-long-url", "click_count": 42, "created_at": "2026-03-07T11:00:00Z", "last_accessed": "2026-03-07T11:59:00Z", "expires_at": "2026-03-08T11:00:00Z" }
- Implemented in
internal/ratelimiterusing Redis fixed windows. - Rule: max 10 URL creations per minute per client IP.
- If exceeded,
POST /shortenreturns 429 Too Many Requests.
- Implemented in
internal/cache. - Caches
short_code -> original_url. - Redirect handler checks Redis first, then falls back to DB and refreshes cache.
- Cache TTL defaults to 24h but is capped by the URL's expiration time when set.
Prerequisites: Docker and Docker Compose.
docker compose up --buildServices:
postgres– PostgreSQL 16redis– Redis 7app– Go service on port8080
The app container is configured via environment variables (see docker-compose.yml and configs/config.example.env).
- Start PostgreSQL and Redis locally.
- Export environment variables (example in
configs/config.example.env). - Run the server:
go run ./cmd/server- Dependency injection: All core components (
URLRepository,URLCache,RateLimiter,URLService) are constructed and wired incmd/server/main.go. - Context usage: Every repository, cache, and rate limiter method accepts a
context.Contextderived from the HTTP request. - Error handling: The service layer exposes domain errors, which handlers translate into HTTP status codes.
- Short code generation: Uses Base62 encoding of the database ID when no custom alias is provided; custom aliases are supported and validated for uniqueness.