Note: This README was generated with the assistance of AI to provide comprehensive project documentation.
A modern, full-featured URL shortener built with Go, featuring server-side rendering with Templ templates, HTMX for dynamic interactions, and styled with Tailwind CSS. The application provides user authentication, link management, visit tracking, and a clean, responsive interface.
- 🔗 URL Shortening: Generate short, unique codes for long URLs using Base62 encoding with UUID v7
- 👤 User Authentication: Secure registration and login with JWT-based authentication
- 📊 Visit Tracking: Track click analytics and IP addresses for each shortened link
- 🎨 Modern UI: Server-side rendered templates with HTMX for dynamic updates
- ⚡ Redis Caching: Fast link resolution with Redis caching layer
- 🐘 PostgreSQL Database: Robust data persistence with proper migrations
- 🔐 Security: Password hashing with bcrypt, JWT tokens, and secure middleware
- 🚀 Production Ready: Docker support with health checks and graceful shutdown
- Go 1.25+: Core application language
- Chi Router: Lightweight and fast HTTP router
- PostgreSQL: Primary database with pgx driver
- Redis: Caching and performance optimization
- Templ: Type-safe Go templating engine
- Goose: Database migration tool
- HTMX: Dynamic HTML interactions without JavaScript
- Tailwind CSS: Utility-first CSS framework
- Alpine.js: Minimal JavaScript for UI interactions
- Docker & Docker Compose: Containerization and orchestration
- Railway: Deployment platform (configured)
limus-shortener/
├── cmd/
│ └── api/
│ └── main.go # Application entry point
├── internal/
│ ├── config/ # Configuration management
│ ├── database/ # Database connection and migrations
│ │ └── migrations/ # SQL migration files
│ ├── domain/ # Domain models and interfaces
│ │ ├── link.go
│ │ └── user.go
│ ├── handler/ # HTTP request handlers
│ │ ├── auth_handler.go
│ │ └── link_handler.go
│ ├── logger/ # Structured logging
│ ├── middleware/ # HTTP middleware (auth, logging, etc.)
│ ├── redis/ # Redis caching layer
│ ├── repository/ # Data access layer
│ │ ├── link.go
│ │ └── user.go
│ ├── router/ # Route definitions
│ ├── server/ # HTTP server configuration
│ ├── service/ # Business logic layer
│ │ ├── link_service.go
│ │ └── user_service.go
│ ├── utils/ # Utility functions
│ │ ├── base62.go # URL shortening algorithm
│ │ ├── jwt.go # JWT token management
│ │ ├── password.go # Password hashing
│ │ └── url.go # URL validation
│ └── validator/ # Request validation
├── views/
│ ├── components/ # Reusable Templ components
│ ├── layouts/ # Page layouts
│ └── pages/ # Page templates
├── static/
│ ├── css/ # Tailwind CSS files
│ └── images/ # Static images
├── tests/
│ ├── helpers/ # Test utilities
│ └── mocks/ # Mock implementations
├── docker-compose.yml # Local development setup
├── Dockerfile # Production container
├── Makefile # Build and development commands
└── go.mod # Go dependencies
- Go: 1.25 or higher
- PostgreSQL: 14+ (or use Docker Compose)
- Redis: 7+ (or use Docker Compose)
- Node.js: 20+ (for Tailwind CSS compilation)
- Docker & Docker Compose: For containerized development (optional)
git clone https://github.com/mustaphalimar/limus-shortener.git
cd limus-shortener# Install Go dependencies
go mod download
# Install Node.js dependencies for Tailwind CSS
npm install
# Install Templ CLI
go install github.com/a-h/templ/cmd/templ@latest
# Install Goose for migrations
go install github.com/pressly/goose/v3/cmd/goose@latestCreate a .env file in the root directory:
# Server Configuration
PORT=8080
HOST=0.0.0.0
APP_ENV=development
BASE_URL=http://localhost:8080
# Database Configuration
DATABASE_URL=postgresql://postgres:password@localhost:5432/limus_shortener?sslmode=disable
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=password
DB_NAME=limus_shortener
DB_SSL_MODE=disable
DB_MAX_OPEN_CONNS=25
DB_IDLE_OPEN_CONNS=25
# Redis Configuration
REDIS_ADDRESS=localhost:6379
REDIS_USERNAME=
REDIS_PASSWORD=
REDIS_DB=0
# JWT Configuration
JWT_SECRET=your-secret-key-here-change-in-production
JWT_EXPIRATION_HOURS=24
JWT_REFRESH_HOURS=168
JWT_ISSUER=limus-shortener
# Logger Configuration
LOG_LEVEL=info
LOG_FORMAT=text# Start PostgreSQL and Redis
docker-compose up -d postgres redis
# Run migrations
make migrate-up# Create database
createdb limus_shortener
# Run migrations
goose -dir internal/database/migrations postgres "your-connection-string" upStart the development server with hot reload for Templ, CSS, and Go files:
make devThis will start the server at http://localhost:8080
# Generate Templ templates
make templ
# Build CSS
npm run build:css
# Build binary
make build
# Run the binary
./bin/limus_shortener# Build and run all services
docker-compose up --build
# Run in detached mode
docker-compose up -d
# View logs
docker-compose logs -f app
# Stop services
docker-compose downmake help # Display all available commands
make run # Run the application
make dev # Start development mode with hot reload
make build # Build the application binary
make templ # Generate Templ templates
make watch-templ # Watch and regenerate Templ files
make watch-css # Watch and rebuild CSS
make test # Run tests
make clean # Clean build artifacts
make migrate-up # Run database migrations
make migrate-down # Rollback database migrations
make migrate-status # Check migration statusGET /register- Registration pagePOST /register- Create new user accountGET /login- Login pagePOST /login- Authenticate userPOST /logout- Logout user
GET /- Home page (authenticated users)POST /api/links- Generate shortened linkGET /:shortLink- Redirect to original URLGET /my-links- View user's linksDELETE /api/links/:id- Delete a link
The application follows a clean architecture pattern with clear separation of concerns:
- Handler Layer: HTTP request/response handling and input validation
- Service Layer: Business logic and orchestration
- Repository Layer: Data access and persistence
- Domain Layer: Core business entities and interfaces
The application uses a Base62 encoding scheme with UUID v7 (time-based UUIDs):
- Generate a UUID v7 timestamp-based identifier
- Convert UUID bytes to a big integer
- Encode the integer using Base62 (0-9, A-Z, a-z)
- Truncate to 10 characters for a short, unique code
This approach ensures:
- Collision resistance through UUID v7
- URL-safe characters only
- Time-ordered generation for better database indexing
- Approximately 839 quadrillion possible combinations
Redis is used as a caching layer for link resolution:
- Cache hits return immediately without database query
- Cache misses fetch from PostgreSQL and populate cache
- TTL-based expiration for automatic cache management
Note: Testing suite is still not implemented. Planned testing coverage includes:
- Unit tests for services and utilities
- Integration tests for repositories
- End-to-end API tests
- Mock implementations for external dependencies
- User authentication and profile information
- Bcrypt password hashing
- Timestamps for created_at and updated_at
- Original URL and shortened code
- Foreign key to users table
- Visit counter
- Indexed on short_link and user_id
- Visit tracking with timestamps
- IP address recording
- Foreign key to links table
- Password Security: Passwords hashed with bcrypt (cost factor 10)
- JWT Tokens: Secure token-based authentication with expiration
- Input Validation: URL validation and sanitization
- SQL Injection: Prepared statements via pgx driver
- HTTPS: Recommended for production deployments
- Environment Variables: Sensitive data stored in environment variables
The project includes a railway.toml configuration for easy deployment:
# Install Railway CLI
npm i -g @railway/cli
# Login to Railway
railway login
# Deploy
railway up- Build the Docker image
- Set environment variables
- Run database migrations
- Start the container with proper health checks
- Configure reverse proxy (Nginx/Caddy) for HTTPS
- Connection Pooling: Configured for PostgreSQL (max 25 connections)
- Redis Caching: Sub-millisecond link resolution
- Compression: HTTP response compression (level 5)
- Static Assets: Minified CSS in production
- Database Indexing: Optimized queries with proper indexes
- 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
This project is open source and available under the MIT License.
- Built with Go
- Templating with Templ
- Routing with Chi
- Dynamic interactions with HTMX
- Styled with Tailwind CSS
Mustapha Limar
- GitHub: @mustaphalimar
For issues, questions, or contributions, please open an issue on the GitHub repository.