A URL shortening service built in Go with comprehensive testing. This is a development/learning project.
- URL shortening with base62 encoding
- Dual storage backends (in-memory or Redis) with atomic operations
- HTTP server with Gin framework
- Middleware (CORS, logging, recovery, validation)
- Environment-based configuration
- Graceful shutdown with timeout handling
- Test coverage (unit, integration, benchmark, race detection)
- URL expiration support
- Access statistics tracking
POST /urls
Content-Type: application/json
{
"long_url": "https://www.example.com/very/long/path",
"expiration_date": "2025-12-31T23:59:59Z" // optional
}Response:
{
"short_url": "http://localhost:8080/1"
}GET /{shortCode}Returns 302 Found with Location header pointing to the original URL.
GET /urls/{shortCode}/statsResponse:
{
"short_code": "1",
"long_url": "https://www.example.com/very/long/path",
"access_count": 42,
"created_at": "2025-07-19T17:00:00Z",
"expiration_date": "2025-12-31T23:59:59Z"
}GET /health- Go 1.21 or later
- Docker (for Redis storage)
# Clone the repository
git clone <repository-url>
cd tiny-url
# Install dependencies
go mod tidy
# Build the service
go build -o tiny-url-service# In-memory storage (quick start)
go run .
# With Redis persistence
docker-compose up -d
STORAGE_TYPE=redis go run .The service is deployed on Railway with managed Redis:
- Demo URL: https://tiny-url-production.up.railway.app
- Health Check: https://tiny-url-production.up.railway.app/health
- API Testing: Use the live API for demonstrations
# Start Redis locally
docker-compose up -d
# Stop Redis
docker-compose down
# Access Redis CLI
docker exec -it tiny-url-redis redis-cli| Variable | Default | Description |
|---|---|---|
PORT |
8080 |
Server port |
GIN_MODE |
debug |
Gin mode (debug, release, test) |
BASE_URL |
http://localhost:8080 |
Base URL for short links |
STORAGE_TYPE |
memory |
Storage backend (memory or redis) |
REDIS_URL |
redis://localhost:6379/0 |
Redis connection URL |
READ_TIMEOUT |
10s |
HTTP read timeout |
WRITE_TIMEOUT |
10s |
HTTP write timeout |
IDLE_TIMEOUT |
60s |
HTTP idle timeout |
The project includes a docker-compose.yml file for easy Redis setup:
services:
redis:
image: redis:7-alpine
container_name: tiny-url-redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --appendonly yes --appendfsync everysec
restart: unless-stopped| Feature | In-Memory | Redis |
|---|---|---|
| Data Persistence | ❌ Lost on restart | ✅ Persists across restarts |
| Multiple Instances | ❌ Single instance only | ✅ Supports multiple instances |
| Performance | ⚡ Fastest | 🚀 Fast (network overhead) |
| Memory Usage | 💾 Process memory | 💾 Redis memory |
| Setup Complexity | ✅ Zero setup | 🐳 Requires Docker/Redis |
| Production Ready | ❌ Development only | ✅ Production ready |
URLs are stored as JSON in Redis with the following structure:
# Keys
counter # Atomic counter for unique IDs
url:{shortCode} # URL mapping data
# Example data
GET url:1
> {"id":1,"short_code":"1","long_url":"https://example.com","created_at":"2025-07-19T17:30:00Z"}# Run complete test suite (39 tests)
go test ./... -v
# With coverage report
go test ./... -cover
# Race condition detection
go test ./... -race- Storage: 92.4% coverage (Redis + Memory)
- Middleware: 100% coverage (Rate limiting)
- Utils: 96.8% coverage (Encoding + Validation)
- Integration: Full API coverage
# Storage tests (with Redis mocking)
go test ./storage -v
# Rate limiter tests
go test ./middleware -v
# Integration tests
go test ./tests -v
# Utility tests
go test ./utils -vtiny-url/
├── main.go # Application entry point
├── config/
│ └── config.go # Environment configuration
├── models/
│ └── url.go # Data models
├── storage/
│ ├── interface.go # Storage interface
│ ├── memory.go # In-memory implementation
│ └── redis.go # Redis implementation
├── handlers/
│ ├── url_handlers.go # HTTP request handlers
│ └── server.go # Server setup and middleware
├── utils/
│ ├── encoding.go # Base62 encoding/decoding
│ └── validation.go # URL validation
├── tests/
│ ├── integration_test.go # Integration tests
│ └── benchmark_test.go # Performance benchmarks
├── middleware/
│ └── rate_limiter_test.go # Rate limiting tests
├── storage/
│ ├── redis_test.go # Redis storage tests (with mocking)
│ └── memory_test.go # Memory storage tests
└── docs/
├── ARCHITECTURE.md # System architecture documentation
└── API.md # API reference and examples
- Converts numeric IDs to short, URL-safe strings
- Character set:
0-9A-Za-z(62 characters) - Collision-free through atomic counter incrementation
In-Memory Storage:
- Mutex-protected concurrent access
- Atomic counter for unique ID generation
- Zero-allocation retrieval operations
- Fast development/testing, data lost on restart
Redis Storage:
- Persistent data across restarts
- Atomic counters using Redis INCR
- JSON serialization of URL mappings
- Support for multiple app instances
- Production-ready with data durability
- Environment-based configuration
- Structured logging with Gin
- Panic recovery middleware
- CORS support
- Content-type validation
- Graceful shutdown with signal handling
Based on benchmark tests:
In-Memory Storage:
| Operation | Throughput | Memory per Op |
|---|---|---|
| Store URL | ~2M ops/sec | 201 B |
| Retrieve URL | ~13M ops/sec | 0 B |
| Create Short URL (HTTP) | ~50K req/sec | - |
| Redirect (HTTP) | ~100K req/sec | - |
Redis Storage:
| Operation | Throughput | Memory per Op |
|---|---|---|
| Store URL | ~50K ops/sec | 512 B |
| Retrieve URL | ~100K ops/sec | 256 B |
| Create Short URL (HTTP) | ~10K req/sec | - |
| Redirect (HTTP) | ~20K req/sec | - |
Race Condition Testing: ✅ All concurrent access tests pass with -race flag for both storage backends
- Architecture Guide - System design, components, and data flow
- API Reference - Complete API documentation with examples
- Project Plan - Development phases and Railway deployment setup
- 39 comprehensive tests with Redis mocking
- 92.4% storage coverage, 100% middleware coverage
- Integration tests cover all API endpoints
- Race detection prevents concurrency bugs
- Benchmark tests ensure performance
- ✅ Persistent Storage: Redis implementation complete
- ✅ Distributed Counter: Redis INCR provides atomic counters across instances
- ✅ Rate Limiting: Per-IP token bucket (20 req/min) implemented
- Security: Add authentication, input sanitization
- Monitoring: Add metrics, health checks, observability
- Error Handling: More robust error responses and logging
- Configuration: More comprehensive config validation
- Performance: Connection pooling, caching, optimization
- Deployment: Containerization, CI/CD, infrastructure