A complete URL shortening service with a modern React frontend and Node.js backend, featuring analytics, rate limiting, caching, and real-time statistics.
- 🌓 Dark/Light Mode - Toggle between themes with persistent storage
- 🔗 URL Shortening - Shorten URLs with optional custom aliases and expiration dates
- 📊 Trending URLs - View the most clicked shortened URLs
- 📈 Analytics Dashboard - Visualize daily click statistics with interactive charts
- 🎨 Modern UI - Beautiful, responsive design with smooth animations
- ⚡ Fast Redirects - Redis caching for instant URL lookups
- 🛡️ Rate Limiting - Protect API endpoints from abuse
- 📊 Analytics - Track clicks with Redis (ultra-fast in-memory analytics)
- 🔒 URL Validation - Comprehensive input validation
- ⏰ Expiration Support - Set expiration dates for shortened URLs
- 🎯 Custom Aliases - Create memorable short URLs
- Node.js (v18 or higher)
- MongoDB (v5 or higher)
- Redis (v6 or higher) - Required for analytics and caching
- Navigate to the backend directory:
cd backend- Install dependencies:
npm install- Create a
.envfile (copy from.env.example):
cp .env.example .env- Update
.envwith your configuration:
MONGO_URL=mongodb://localhost:27017/url-shortener
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
PORT=5000
BASE_URL=http://localhost:5000- Start the backend server:
npm run devThe backend will run on http://localhost:5000
- Navigate to the frontend directory:
cd frontend- Install dependencies:
npm install- Create a
.envfile (optional, defaults tohttp://localhost:5000/api):
VITE_API_URL=http://localhost:5000/api- Start the development server:
npm run devThe frontend will run on http://localhost:5173
URL Shortener/
├── backend/
│ ├── src/
│ │ ├── config/ # Database and service configurations
│ │ ├── controllers/ # Request handlers
│ │ ├── models/ # MongoDB models
│ │ ├── routes/ # API routes
│ │ ├── utils/ # Utility functions
│ │ ├── services/ # Analytics service (Redis-based)
│ │ └── utils/ # Utility functions
│ └── package.json
├── frontend/
│ ├── src/
│ │ ├── components/ # React components
│ │ ├── contexts/ # React contexts (Theme)
│ │ ├── services/ # API service layer
│ │ └── App.jsx
│ └── package.json
└── README.md
POST /api/shorten- Create a shortened URL{ "longUrl": "https://example.com", "customAlias": "optional-alias", "expiresAt": "2024-12-31T23:59:59Z" }
GET /api/analytics/top-links- Get top 10 most clicked URLs (powered by Redis Sorted Sets)GET /api/analytics/daily-clicks?shortId=<id>&days=<n>- Get daily click statistics (powered by Redis Hashes)
GET /:shortId- Redirect to original URL
- Input validation for URLs
- Custom alias support (3-20 characters)
- Optional expiration date
- One-click copy functionality
- Real-time top 10 URLs
- Click count display
- Copy and open functionality
- Auto-refresh on new URL creation
- Daily clicks visualization
- Bar and line chart modes
- Responsive design
- Interactive tooltips
- Enhanced URL Validation - Proper URL format checking
- Better Error Handling - Comprehensive error messages
- Custom Alias Validation - Format and length validation
- Expiration Date Validation - Ensures future dates only
- Redis Connection Fix - Proper connection state checking
- IP Address Handling - Fallback for IP extraction
- Analytics Enhancement - Returns full URL information
- Route Protection - Excludes API paths from shortId matching
- Start MongoDB and Redis (if using)
- Start the backend server:
cd backend && npm run dev - Start the frontend:
cd frontend && npm run dev - Open
http://localhost:5173in your browser - Shorten URLs and view analytics!
Below are UML diagrams describing the backend system architecture and flows.
graph TD
subgraph "Clients"
U["User (Browser)"]
A["Admin Panel (Flutter)"]
end
subgraph "Backend"
API["Express App"]
Rts["Routes: /api, /api/analytics"]
Ctrls["Controllers: UrlController, AnalyticsController"]
Utils["Utils: RateLimiter, Shortener"]
Analytics["Analytics Service (Redis)"]
end
subgraph "Data Stores"
R["Redis"]
M["MongoDB"]
end
U -->|"POST /api/shorten\nGET /:shortId"| API
A -->|"GET /api/analytics/*"| API
API --> Rts
Rts --> Ctrls
Ctrls -->|"CRUD"| M
API -->|"GET/SET rate, url:*"| R
API -->|"trackClick()"| Analytics
Analytics -->|"INCR, HINCRBY, ZINCRBY"| R
Ctrls -->|"getTrendingUrls()"| Analytics
classDiagram
class Url {
+shortId: String
+longUrl: String
+createdAt: Date
+clicks: Number
+expiresAt: Date
}
class Analytics {
+shortId: String
+ip: String
+userAgent: String
+referrer: String
+timestamp: Date
}
class UrlController {
+shortenUrl(req, res)
}
class AnalyticsController {
+getTopLinks(req, res)
+getDailyClicks(req, res)
}
class RateLimiter {
+invoke(limit, windowSeconds) middleware
}
class Shortener {
+generateShortId(): String
}
class RedisClient
class AnalyticsService {
+trackClick(shortId, ip, meta)
+getTotalClicks(shortId)
+getDailyClicks(shortId, days)
+getTrendingUrls(limit)
}
class ExpressApp {
+startServer()
+GET(":shortId")
}
ExpressApp --> UrlController
ExpressApp --> AnalyticsController
ExpressApp --> RedisClient
ExpressApp --> AnalyticsService
UrlController --> Shortener
UrlController --> Url
AnalyticsController --> AnalyticsService
AnalyticsController --> Url
RateLimiter --> RedisClient
AnalyticsService --> RedisClient
sequenceDiagram
autonumber
actor U as User
participant API as Express App
participant RL as RateLimiter (Redis)
participant CTRL as UrlController
participant DB as MongoDB (Url)
U->>API: POST /api/shorten {longUrl, customAlias, expiresAt}
API->>RL: check ip rate
alt over limit
RL-->>API: 429 Too Many Requests
API-->>U: 429 error
else allowed
RL-->>API: ok
API->>CTRL: shortenUrl()
alt customAlias provided
CTRL->>DB: findOne({shortId: customAlias})
DB-->>CTRL: exists?
end
CTRL->>DB: save({shortId,longUrl,expiresAt})
CTRL-->>API: {shortUrl,expiresAt}
API-->>U: 201 Created
end
sequenceDiagram
autonumber
actor U as User
participant API as Express App
participant Cache as Redis
participant DB as MongoDB (Url)
participant Analytics as Analytics Service
U->>API: GET /:shortId
API->>Cache: GET url:shortId
alt cache hit
Cache-->>API: longUrl
API->>Analytics: trackClick(shortId, ip)
Analytics->>Cache: INCR url:clicks:shortId
Analytics->>Cache: HINCRBY url:daily:shortId date
Analytics->>Cache: ZINCRBY trending:urls 1 shortId
API-->>U: 302 Redirect longUrl
else cache miss
Cache-->>API: null
API->>DB: findOne({shortId})
alt expired
API->>Cache: DEL url:shortId
API-->>U: 410 Gone
else found
DB-->>API: entry
API->>Cache: SET url:shortId longUrl EX=3600
API->>Analytics: trackClick(shortId, ip)
Analytics->>Cache: INCR url:clicks:shortId
Analytics->>Cache: HINCRBY url:daily:shortId date
Analytics->>Cache: ZINCRBY trending:urls 1 shortId
API-->>U: 302 Redirect longUrl
else not found
DB-->>API: null
API-->>U: 404 Not Found
end
end
graph LR
subgraph "Client"
Browser["User Browser"]
Admin["Admin Panel (Flutter app)"]
end
subgraph "App Server"
Express["Node.js Express API"]
end
subgraph "Data Layer"
Mongo["MongoDB"]
Redis["Redis (Cache + Analytics)"]
end
Browser --> Express
Admin --> Express
Express --> Mongo
Express --> Redis