A secure, production-ready URL shortening service with analytics, expiration, and comprehensive security validation.
Converts long URLs into short, shareable links with tracking and security features.
Example:
https://example.com/very/long/url/path → https://short.ly/abc123
- 3-Layer Validation: Middleware → Controller → Utils
- Early Exit: Block malicious requests before DB lookup
- SQL injection & XSS detection
- Private IP blocking (SSRF prevention)
- Phishing URL detection
- DNS validation before shortening
- MongoDB indexes for O(log n) lookups
- TTL index auto-deletes expired URLs
- Atomic click tracking (no race conditions)
- Click history capped at 100 entries
- Stateless design (ready for horizontal scaling)
- Efficient schema with compound indexes
- Soft delete (isActive flag)
GET /v1/healthPurpose: Check if server is running
Response:
{
"status": true,
"message": "Server is running",
"timestamp": "2025-01-15T10:30:00Z"
}Status Code: 200 OK
POST /v1/shorten
Content-Type: application/jsonPurpose: Create a short URL from a long URL
Request Body:
{
"longUrl": "https://example.com/very/long/url",
"customAlias": "my-link", // Optional (3-20 chars)
"expiresIn": 30 // Optional (days, 1-365)
}Response (Success - 201 Created):
{
"status": true,
"message": "URL shortened successfully",
"data": {
"longUrl": "https://example.com/very/long/url",
"shortUrl": "http://localhost:5000/v1/my-link",
"urlCode": "my-link",
"expiresAt": "2025-02-14T10:30:00Z",
"createdAt": "2025-01-15T10:30:00Z"
}
}Response (Error - 400 Bad Request):
{
"status": false,
"message": "Validation failed",
"errors": [
"longUrl is required",
"customAlias must be 3-20 characters (alphanumeric, underscore, hyphen only)"
]
}Validation Rules:
- ✅
longUrl: Required, 10-2048 chars, must start with http/https - ✅
customAlias: Optional, 3-20 alphanumeric +_-only - ✅
expiresIn: Optional, 1-365 days
Status Codes:
201- URL created successfully400- Validation failed409- Custom alias already exists500- Server error
GET /v1/:urlCodePurpose: Redirect to original long URL and track analytics
Example Request:
GET /v1/my-linkBehavior:
| Scenario | Response | Status Code |
|---|---|---|
| ✅ Valid & Active | Redirects to longUrl |
302 Found |
| ❌ URL Expired | Error message | 410 Gone |
| ❌ URL Not Found | Error message | 404 Not Found |
| ❌ URL Inactive | Error message | 404 Not Found |
Success Response (Redirect):
HTTP/1.1 302 Found
Location: https://example.com/very/long/url
Error Response (Expired - 410 Gone):
{
"status": false,
"message": "URL has expired"
}Error Response (Not Found - 404):
{
"status": false,
"message": "URL not found"
}What Gets Tracked:
- ✅ Click count increment
- ✅ IP address
- ✅ User agent (browser/device)
- ✅ Referer (where they came from)
- ✅ Timestamp
- ❌ SQL injection patterns (
union,select,--,;) - ❌ XSS attempts (
<script>,javascript:,onerror=) - ❌ Private IPs (
127.0.0.1,192.168.x.x,10.x.x.x) - ❌ Dangerous files (
.exe,.bat,.php,.sh) - ❌ Adult/gambling/illegal keywords
- ❌ Phishing-like URLs (too many subdomains, suspicious patterns)
- ❌ Malicious protocols (
data:,file:,javascript:)
1. sanitizeInputs → Trim whitespace, remove null bytes
2. checkSuspiciousActivity → Block SQL/XSS patterns
3. validateShortenRequest → Check required fields
4. Controller → Business logic
5. validateUrl (Utils) → DNS + Content-Type + Security checks
git clone <repository-url>
cd url-shortener
npm installCreate .env file in root:
# Server Configuration
NODE_ENV=production
PORT=3000
# Database
MONGO_URL=mongodb://localhost:27017/url-shortener
# Application URLs
VITE_BACKEND_URL=https://api.yourdomain.com
FRONTEND_URL=https://yourdomain.com
# CORS Configuration
# Comma-separated list of allowed origins
ALLOWED_ORIGINS=https://yourdomain.com,http://localhost:3000,http://localhost:5173
# Rate Limiting (Optional overrides)
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100
# Logging
LOG_LEVEL=info
Environment Variables Explained:
| Variable | Required | Description | Example |
|---|---|---|---|
PORT |
✅ Yes | Server port | 5000 |
MONGO_URI |
✅ Yes | MongoDB connection string | mongodb://localhost:27017/url-shortener |
BASE_URL |
✅ Yes | Your domain (for short URLs) | http://localhost:5000 |
NODE_ENV |
❌ No | Environment mode | development / production |
RATE_LIMIT_WINDOW |
❌ No | Rate limit time window (minutes) | 15 |
RATE_LIMIT_MAX |
❌ No | Max requests per window | 100 |
# Local MongoDB
mongod
# OR use Docker
docker run -d -p 27017:27017 --name mongodb mongo:latest# Development (with auto-restart)
npm run dev
# Production
npm startServer runs at: http://localhost:5000
url-shortener/
├── controller/
│ └── urlController.js # Business logic (createUrl, getUrl)
├── middlewares/
│ └── validationMiddleware.js # Request validation & security checks
├── models/
│ └── urlModel.js # MongoDB schema with indexes
├── routes/
│ └── urlRoutes.js # API endpoints (3 routes)
├── utils/
│ └── urlValidator.js # Deep URL validation (DNS, security)
├── .env # Environment variables
├── index.js # Entry point
└── package.json
1. Health Check:
curl http://localhost:5000/v1/health2. Shorten URL (without custom alias):
curl -X POST http://localhost:5000/v1/shorten \
-H "Content-Type: application/json" \
-d '{
"longUrl": "https://github.com/bidyut10"
}'3. Shorten URL (with custom alias & expiration):
curl -X POST http://localhost:5000/v1/shorten \
-H "Content-Type: application/json" \
-d '{
"longUrl": "https://github.com/bidyut10",
"customAlias": "bidyut",
"expiresIn": 30
}'4. Access Short URL (redirects):
curl -L http://localhost:5000/v1/bidyut- Create new request
- Set method to
POST - URL:
http://localhost:5000/v1/shorten - Headers:
Content-Type: application/json - Body (raw JSON):
{
"longUrl": "https://example.com",
"customAlias": "test",
"expiresIn": 7
}const response = await fetch('http://localhost:5000/v1/shorten', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
longUrl: 'https://example.com',
customAlias: 'my-link',
expiresIn: 30
})
});
const data = await response.json();
console.log(data.data.shortUrl); // http://localhost:5000/v1/my-linkOptimized for performance:
// Fast lookups by URL code
{ urlCode: 1, isActive: 1 }
// Auto-delete expired URLs (TTL index)
{ expiresAt: 1 }
// Analytics queries
{ createdAt: 1, expiresAt: 1 }{
"express": "^4.18.0", // Web framework
"mongoose": "^7.0.0", // MongoDB ODM
"validator": "^13.9.0", // URL validation
"node-fetch": "^2.6.7", // HTTP requests (for URL checking)
"dotenv": "^16.0.0", // Environment variables
"dns": "built-in" // DNS resolution
}Install all:
npm install express mongoose validator node-fetch dotenv- Default: Random 6-character alphanumeric (e.g.,
a3X9kL) - Custom: 3-20 characters (alphanumeric,
_,-)
- Default: Never expires (
null) - Custom: 1-365 days from creation
- Stores last 100 clicks per URL
- Tracks: IP, User-Agent, Referer, Timestamp
- Updates atomically (no race conditions)
| Code | Meaning | Used In |
|---|---|---|
| 200 | OK | Health check |
| 201 | Created | URL shortened successfully |
| 302 | Found (Redirect) | Redirect to original URL |
| 400 | Bad Request | Validation failed |
| 404 | Not Found | URL doesn't exist or inactive |
| 409 | Conflict | Custom alias already taken |
| 410 | Gone | URL expired |
| 500 | Server Error | Database/internal error |
✅ Custom short URLs (aliases)
✅ Expiration dates (1-365 days)
✅ Click analytics (last 100 clicks)
✅ Security validation (SQL/XSS/SSRF)
✅ DNS verification before shortening
✅ Auto-cleanup of expired URLs (TTL)
✅ Soft delete (disable without deletion)
✅ Private IP blocking
✅ Phishing detection
✅ Content-type validation
User wants to shorten: https://example.com/long/url
1. POST /v1/shorten
{
"longUrl": "https://example.com/long/url",
"customAlias": "my-link"
}
2. Server validates:
✅ Sanitizes input
✅ Checks for SQL/XSS
✅ Validates URL format
✅ Checks DNS resolution
✅ Verifies content-type
✅ Blocks malicious content
3. Server creates short URL:
http://localhost:5000/v1/my-link
4. User shares short URL
5. Someone clicks: GET /v1/my-link
6. Server:
✅ Finds URL in DB
✅ Checks if active & not expired
✅ Tracks click (IP, browser, etc.)
✅ Redirects to: https://example.com/long/url
MIT License - Feel free to use in your projects!
Bidyut Kundu
Building secure, scalable systems one API at a time 🚀
Need help? Open an issue or reach out!