A full-stack URL shortening service with analytics tracking, custom short codes, link expiration, and an admin dashboard.
| Metric | Value |
|---|---|
| Total SLOC | 7,451 |
| Source Files | 63 |
| .ts | 3,992 |
| .md | 1,682 |
| .tsx | 1,152 |
| .sql | 257 |
| .json | 165 |
- URL Shortening: Convert long URLs to short, memorable links
- Custom Short Codes: Create branded links with custom short codes
- Analytics Tracking: Track clicks, referrers, and device types
- Link Expiration: Set optional expiration dates for links
- User Authentication: Session-based authentication with role support
- Admin Dashboard: System stats, URL management, user management, and key pool monitoring
┌─────────────────┐ ┌─────────────────┐
│ Frontend │────▶│ Backend API │
│ (React/Vite) │ │ (Express.js) │
└─────────────────┘ └────────┬────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────────┐
│ Redis │ │PostgreSQL│ │Key Pool Svc │
│ (Cache) │ │ (DB) │ │(Pre-gen IDs)│
└─────────┘ └─────────┘ └─────────────┘
See architecture.md and system-design-answer-fullstack.md for detailed design documentation.
- Node.js 18+ or 20+
- Docker and Docker Compose (recommended) OR:
- PostgreSQL 16
- Redis 7
The easiest way to get started is using Docker for infrastructure:
# 1. Start PostgreSQL and Redis
docker-compose up -d
# 2. Install backend dependencies
cd backend
npm install
# 3. Start the backend server
npm run dev
# 4. In a new terminal, install frontend dependencies
cd frontend
npm install
# 5. Start the frontend dev server
npm run devThe application will be available at:
- Frontend: http://localhost:5173
- Backend API: http://localhost:3000
If you prefer native services instead of Docker:
# Install services
brew install postgresql@16 redis
# Start services
brew services start postgresql@16
brew services start redis# Create database and user
psql postgres <<EOF
CREATE USER bitly WITH PASSWORD 'bitly_password';
CREATE DATABASE bitly OWNER bitly;
EOF
# Initialize schema
psql -U bitly -d bitly -f backend/init.sql# Backend
cd backend
npm install
npm run dev
# Frontend (new terminal)
cd frontend
npm install
npm run devFor testing load balancing and distributed scenarios:
# Terminal 1: Server on port 3001
cd backend
npm run dev:server1
# Terminal 2: Server on port 3002
npm run dev:server2
# Terminal 3: Server on port 3003
npm run dev:server3An admin user is created automatically:
- Email: admin@bitly.local
- Password: admin123
Register a new user.
curl -X POST http://localhost:3000/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "password123"}'Response:
{
"id": "uuid",
"email": "user@example.com",
"role": "user",
"created_at": "2024-01-15T10:00:00Z"
}Login and receive a session cookie.
curl -X POST http://localhost:3000/api/v1/auth/login \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{"email": "user@example.com", "password": "password123"}'Response:
{
"user": {
"id": "uuid",
"email": "user@example.com",
"role": "user"
},
"token": "session-token"
}Logout and invalidate session.
Get current authenticated user.
Create a short URL.
curl -X POST http://localhost:3000/api/v1/urls \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{
"long_url": "https://example.com/very/long/url",
"custom_code": "my-link",
"expires_in": 86400
}'Request body:
| Field | Type | Required | Description |
|---|---|---|---|
| long_url | string | Yes | The URL to shorten |
| custom_code | string | No | Custom short code (4-20 chars) |
| expires_in | number | No | Expiration time in seconds |
Response:
{
"short_url": "http://localhost:3000/my-link",
"short_code": "my-link",
"long_url": "https://example.com/very/long/url",
"created_at": "2024-01-15T10:00:00Z",
"expires_at": "2024-01-16T10:00:00Z",
"click_count": 0,
"is_custom": true
}List user's URLs (requires authentication).
curl http://localhost:3000/api/v1/urls?limit=50&offset=0 \
-b cookies.txtGet URL details.
Update URL (deactivate or change expiration).
curl -X PATCH http://localhost:3000/api/v1/urls/my-link \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{"is_active": false}'Delete (deactivate) a URL.
Redirect to the original URL.
curl -L http://localhost:3000/my-link
# Redirects to https://example.com/very/long/urlGet analytics for a URL (requires authentication).
Response:
{
"short_code": "my-link",
"total_clicks": 150,
"clicks_by_day": [
{"date": "2024-01-15", "count": 50},
{"date": "2024-01-14", "count": 100}
],
"top_referrers": [
{"referrer": "https://twitter.com", "count": 80},
{"referrer": "Direct", "count": 70}
],
"devices": [
{"device": "desktop", "count": 100},
{"device": "mobile", "count": 50}
]
}All admin endpoints require admin role authentication.
Get system statistics.
Get global analytics.
List all URLs with filtering.
Query parameters:
limit: Number of results (default: 50)offset: Pagination offsetis_active: Filter by active statusis_custom: Filter by custom codessearch: Search in short_code or long_url
Deactivate a URL.
Reactivate a URL.
List all users.
Update user role.
curl -X PATCH http://localhost:3000/api/v1/admin/users/user-id/role \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{"role": "admin"}'Get key pool statistics.
Add more keys to the pool.
bitly/
├── docker-compose.yml # PostgreSQL and Redis containers
├── backend/
│ ├── package.json
│ ├── tsconfig.json
│ ├── init.sql # Database schema and seed data
│ └── src/
│ ├── index.ts # Express app entry point
│ ├── config.ts # Configuration settings
│ ├── routes/ # API route handlers
│ │ ├── auth.ts
│ │ ├── urls.ts
│ │ ├── analytics.ts
│ │ ├── admin.ts
│ │ └── redirect.ts
│ ├── services/ # Business logic
│ │ ├── authService.ts
│ │ ├── urlService.ts
│ │ ├── keyService.ts
│ │ ├── analyticsService.ts
│ │ └── adminService.ts
│ ├── middleware/ # Express middleware
│ │ ├── auth.ts
│ │ └── errorHandler.ts
│ ├── utils/ # Utilities
│ │ ├── database.ts
│ │ └── cache.ts
│ └── models/ # Type definitions
│ └── types.ts
└── frontend/
├── package.json
├── vite.config.ts
├── tailwind.config.js
└── src/
├── main.tsx
├── App.tsx
├── index.css
├── routes/ # TanStack Router routes
│ ├── __root.tsx
│ ├── index.tsx
│ ├── login.tsx
│ ├── dashboard.tsx
│ └── admin.tsx
├── components/ # React components
│ ├── Header.tsx
│ ├── UrlShortener.tsx
│ ├── UrlList.tsx
│ ├── AuthForms.tsx
│ └── AdminDashboard.tsx
├── stores/ # Zustand state stores
│ ├── authStore.ts
│ └── urlStore.ts
├── services/ # API client
│ └── api.ts
└── types/ # TypeScript types
└── index.ts
| Variable | Default | Description |
|---|---|---|
| PORT | 3000 | Server port |
| HOST | 0.0.0.0 | Server host |
| BASE_URL | http://localhost:3000 | Base URL for short links |
| CORS_ORIGIN | http://localhost:5173 | Allowed CORS origin |
| DB_HOST | localhost | PostgreSQL host |
| DB_PORT | 5432 | PostgreSQL port |
| DB_NAME | bitly | Database name |
| DB_USER | bitly | Database user |
| DB_PASSWORD | bitly_password | Database password |
| REDIS_HOST | localhost | Redis host |
| REDIS_PORT | 6379 | Redis port |
| SERVER_ID | server-{pid} | Unique server identifier |
See claude.md for development insights and design decisions.
# Create a short URL (unauthenticated)
curl -X POST http://localhost:3000/api/v1/urls \
-H "Content-Type: application/json" \
-d '{"long_url": "https://github.com"}'
# Test redirect
curl -L http://localhost:3000/<short_code>
# Login as admin
curl -X POST http://localhost:3000/api/v1/auth/login \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{"email": "admin@bitly.local", "password": "admin123"}'
# Get system stats
curl http://localhost:3000/api/v1/admin/stats -b cookies.txt- URL shortening with pre-generated key pool
- Custom short codes
- Link expiration
- Click analytics
- User authentication (session-based)
- Admin dashboard
- Redis caching for redirects
- Rate limiting
- Docker support
- How We Built Bitly - Lessons from a decade of running a URL shortener at scale
- Base62 Encoding - The encoding scheme commonly used for generating short URL codes
- Instagram Engineering: Generating Unique IDs - How Instagram generates unique IDs at scale, applicable to short code generation
- Designing a URL Shortening Service - Comprehensive system design walkthrough
- How Short Links Work - Technical deep dive into URL shortener architecture
- Redis as a Cache - Redis caching patterns for high-throughput applications
- Analytics at Scale with ClickHouse - Analytics database commonly used for click tracking at scale