A high-performance URL shortener with a dedicated analytics microservice, real-time click tracking, Redis caching, BullMQ queues, and a modern Next.js dashboard.
graph TB
subgraph Client Layer
FE[Next.js Frontend :3000]
end
subgraph API Layer
BE[Backend Service :5001]
AN[Analytics Service :5002]
WK[BullMQ Worker]
end
subgraph Cache Layer
RD[(Redis)]
end
subgraph Data Layer
PG[(PostgreSQL)]
end
FE -->|REST API| BE
FE -->|Stats API| AN
BE -->|1. URL Lookup| RD
RD -->|Cache Miss| PG
BE -->|2. Push Click Event| RD
RD -->|BullMQ Queue| WK
WK -->|3. Enrich & Write| PG
AN -->|Read Stats| PG
BE -.->|Rate Limit Check| RD
style FE fill:#7c3aed,color:#fff,stroke:none
style BE fill:#2563eb,color:#fff,stroke:none
style AN fill:#059669,color:#fff,stroke:none
style WK fill:#d97706,color:#fff,stroke:none
style RD fill:#dc2626,color:#fff,stroke:none
style PG fill:#0ea5e9,color:#fff,stroke:none
sequenceDiagram
participant C as Client
participant B as Backend
participant R as Redis
participant DB as PostgreSQL
participant Q as BullMQ Queue
participant W as Worker
C->>B: GET /:shortCode?utm_source=twitter
B->>R: GET url:shortCode
alt Cache Hit
R-->>B: { id, original_url }
else Cache Miss
B->>DB: SELECT FROM urls
DB-->>B: { id, original_url }
B->>R: SET url:shortCode (TTL 1h)
end
B-->>C: 302 Redirect (instant)
B->>R: INCR visit_count
B->>Q: Push { url_id, ip, ua, utm_* }
Q-->>W: Consume job
W->>R: Check dedup (SET key, 30s TTL)
W->>DB: INSERT url_analytics (enriched)
This is a multi-repo project using Git submodules:
| Submodule | Repo | Description |
|---|---|---|
frontend/ |
dev.ly-frontend | Next.js 16 dashboard & landing page |
backend/ |
dev.ly-backend | Express 5 URL shortener API |
analytics/ |
dev.ly-analytics | Analytics microservice & BullMQ worker |
dev.ly/
├── frontend/ ← git submodule (Next.js)
├── backend/ ← git submodule (Express API)
├── analytics/ ← git submodule (Analytics + Worker)
├── docker-compose.yml
├── .gitmodules
└── README.md ← you are here
# Clone with all submodules
git clone --recurse-submodules https://github.com/dk-a-dev/dev.ly.git
# If already cloned without submodules
git submodule update --init --recursive| Layer | Technology |
|---|---|
| Frontend | Next.js 16, React 19, Tailwind CSS 4, Recharts, Framer Motion |
| Backend | Express 5, JWT, bcrypt, Redis, BullMQ |
| Analytics | Express 5, ua-parser-js, geoip-lite, BullMQ Workers |
| Database | PostgreSQL 15 |
| Cache / Queue | Redis 7 + BullMQ |
| Infrastructure | Docker, Docker Compose |
erDiagram
users {
SERIAL id PK
VARCHAR username UK
VARCHAR email UK
VARCHAR password_hash
TIMESTAMP created_at
}
urls {
SERIAL id PK
TEXT original_url
VARCHAR short_code UK
INTEGER user_id FK
INTEGER visit_count
TIMESTAMP created_at
TIMESTAMP expires_at
}
url_analytics {
SERIAL id PK
INTEGER url_id FK
VARCHAR ip_address
TEXT user_agent
TEXT referrer
VARCHAR country
VARCHAR city
VARCHAR browser
VARCHAR os
VARCHAR device_type
VARCHAR utm_source
VARCHAR utm_medium
VARCHAR utm_campaign
BOOLEAN is_unique
TIMESTAMP visited_at
}
users ||--o{ urls : "creates"
urls ||--o{ url_analytics : "has clicks"
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/auth/register |
✗ | Register user |
POST |
/api/auth/login |
✗ | Login, returns JWT |
GET |
/api/auth/profile |
✓ | Get current user profile |
POST |
/api/url |
✓ | Create short URL |
GET |
/api/url |
✓ | List user's URLs |
DELETE |
/api/url/:id |
✓ | Delete URL |
GET |
/:shortCode |
✗ | Redirect to original URL |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/log |
✗ | Ingest click event |
GET |
/api/stats |
✓ | Get user's aggregate stats |
GET |
/api/stats/urls-series |
✓ | 7-day time series per URL |
GET |
/api/stats/:id |
✓ | Detailed stats for single URL |
| Endpoint | Limit | Behavior |
|---|---|---|
POST /api/auth/* |
10 req/min | Returns 429 |
POST /api/url |
30 req/min | Returns 429 |
GET /api/stats/* |
30 req/min | Returns 429 |
GET /:shortCode |
No limit | Always redirects |
- Docker & Docker Compose
- Node.js 18+ (for local dev)
# Clone with submodules
git clone --recurse-submodules https://github.com/dk-a-dev/dev.ly.git
cd dev.ly
# Start all services
docker-compose up --build -d
# View logs
docker-compose logs -fThis starts: PostgreSQL (:5432) → Redis (:6379) → Backend (:5001) → Analytics (:5002) → Worker → Frontend (:3000)
# Start DB + Redis only
docker-compose up -d db redis
# Backend (terminal 1)
cd backend && npm install && node index.js
# Analytics API (terminal 2)
cd analytics && npm install && node index.js
# Analytics Worker (terminal 3)
cd analytics && node src/workers/worker.js
# Frontend (terminal 4)
cd frontend && npm install && npm run devBackend (.env)
PORT=5001
DB_USER=postgres
DB_PASSWORD=postgres
DB_HOST=localhost
DB_NAME=devly
DB_PORT=5432
JWT_SECRET=supersecretkey_change_me_in_prod
ANALYTICS_URL=http://localhost:5002
REDIS_URL=redis://localhost:6379Analytics (.env)
PORT=5002
DB_USER=postgres
DB_PASSWORD=postgres
DB_HOST=localhost
DB_NAME=devly
DB_PORT=5432
JWT_SECRET=supersecretkey_change_me_in_prod
REDIS_URL=redis://localhost:6379Docker: When running in Docker, use
DB_HOST=db,ANALYTICS_URL=http://analytics:5002,REDIS_URL=redis://redis:6379
| Feature | Description | Status |
|---|---|---|
| Redis URL Cache | Cache shortCode → URL in Redis. Sub-ms redirects. |
✅ Done |
| BullMQ Click Queue | Guaranteed delivery. Zero lost analytics. | ✅ Done |
| Smart Rate Limiting | Redis-backed. Protect APIs, never block redirects. | ✅ Done |
| Click Deduplication | Same IP + URL within 30s = is_unique: false. |
✅ Done |
| UTM Tracking | Parse utm_source, utm_medium, utm_campaign. |
✅ Done |
| QR Code Generation | Auto-generate QR codes for each short URL. | 🔲 Planned |
| Link Expiry Dashboard | Manage and extend URL expiration from the UI. | 🔲 Planned |