Source: Jolli-sample-repos/url-shortener Last Updated: 4/8/2026
URL Shortener
A complete URL shortening service with a TypeScript/Express backend and vanilla JavaScript frontend. Features custom codes, expiration support, click tracking, and analytics.
Features
Backend
- URL Shortening: Convert long URLs to short, shareable links
- Custom Codes: Create memorable custom short codes
- Auto-generation: Automatic 6-character alphanumeric codes
- Expiration: Set expiration dates for temporary URLs
- Click Tracking: Track clicks and last accessed time
- Duplicate Prevention: Returns existing short code for duplicate long URLs
- Pagination: Paginated URL listing
- Analytics: Global stats and top URLs by clicks
- OpenAPI Documentation: Interactive Swagger UI
Frontend
- Clean, responsive UI with purple gradient theme
- Shorten URLs with optional custom codes and expiration
- Copy shortened URLs to clipboard
- View detailed URL statistics
- Recent URLs history (localStorage)
- Top 10 most clicked URLs leaderboard
Installation
# Install backend dependencies
cd backend
npm installRunning the Application
Backend
cd backend
# Development
npm run dev
# OR Build and run production
npm run build
npm startServer starts at: http://localhost:3001
Frontend
cd frontend
# Option 1: Using Python
python -m http.server 8080
# Option 2: Using Node.js http-server
npx http-server -p 8080Navigate to http://localhost:8080/index.html
API Documentation
Swagger UI: http://localhost:3001/api-docs
API Endpoints
Base URL: http://localhost:3001/api/v1
| Method | Endpoint | Description |
|---|---|---|
| POST | /urls | Create short URL |
| GET | /urls/:shortCode | Get URL details |
| GET | /urls | List all URLs (paginated) |
| PUT | /urls/:shortCode | Update long URL |
| DELETE | /urls/:shortCode | Delete short URL |
| GET | /r/:shortCode | Redirect to long URL |
| GET | /stats | Get global statistics |
| GET | /stats/top | Get top URLs by clicks |
Usage Examples
Create Short URL
curl -X POST http://localhost:3000/api/v1/urls \
-H "Content-Type: application/json" \
-d '{
"longUrl": "https://www.example.com/very/long/url/path"
}'Response:
{
"shortCode": "aB3xY9",
"longUrl": "https://www.example.com/very/long/url/path",
"createdAt": "2025-10-27T12:00:00.000Z",
"clicks": 0
}Create with Custom Code
curl -X POST http://localhost:3000/api/v1/urls \
-H "Content-Type: application/json" \
-d '{
"longUrl": "https://github.com/myrepo",
"customCode": "github"
}'Create with Expiration
curl -X POST http://localhost:3000/api/v1/urls \
-H "Content-Type: application/json" \
-d '{
"longUrl": "https://event.com/registration",
"expiresAt": "2025-12-31T23:59:59.000Z"
}'Get URL Details
curl http://localhost:3000/api/v1/urls/aB3xY9Response:
{
"shortCode": "aB3xY9",
"longUrl": "https://www.example.com/very/long/url/path",
"createdAt": "2025-10-27T12:00:00.000Z",
"clicks": 15,
"lastAccessedAt": "2025-10-27T14:30:00.000Z"
}List URLs (Paginated)
# Default: page 1, limit 10
curl http://localhost:3000/api/v1/urls
# Custom pagination
curl "http://localhost:3000/api/v1/urls?page=2&limit=20"Response:
{
"urls": [...],
"total": 45,
"page": 1,
"limit": 10,
"hasMore": true
}Redirect to Long URL
curl http://localhost:3000/r/aB3xY9Response: HTTP 302 redirect to long URL (clicks incremented)
Update Long URL
curl -X PUT http://localhost:3000/api/v1/urls/aB3xY9 \
-H "Content-Type: application/json" \
-d '{
"longUrl": "https://www.example.com/new/path"
}'Delete Short URL
curl -X DELETE http://localhost:3000/api/v1/urls/aB3xY9Get Statistics
curl http://localhost:3000/api/v1/statsResponse:
{
"totalUrls": 100,
"totalClicks": 1543,
"activeUrls": 95,
"expiredUrls": 5
}Get Top URLs
# Top 10 (default)
curl http://localhost:3000/api/v1/stats/top
# Top 5
curl "http://localhost:3000/api/v1/stats/top?limit=5"Features in Detail
Short Code Generation
- Auto-generated: 6-character alphanumeric codes (e.g.,
aB3xY9) - Character set:
0-9A-Za-z(62 characters) - Collision detection: Regenerates if code exists
- Custom codes: 4-20 characters, alphanumeric only
Duplicate Prevention
When creating a short URL for a long URL that already exists:
- Returns the existing short code (200 status)
- Only if the existing URL hasn’t expired
- Prevents URL bloat
URL Expiration
- Set
expiresAtin ISO 8601 format - Expired URLs return 410 Gone status on redirect
- Excluded from top URLs statistics
- Counted in global stats as
expiredUrls
Click Tracking
- Increments on each redirect access
- Tracks
lastAccessedAttimestamp - Viewable in URL details
- Used for top URLs ranking
Data Models
ShortUrl
{
shortCode: string; // e.g., "aB3xY9"
longUrl: string; // Full URL
createdAt: string; // ISO 8601
expiresAt?: string; // ISO 8601 (optional)
clicks: number; // Total redirects
lastAccessedAt?: string; // ISO 8601 (optional)
}Validation Rules
Long URL
- Must be a valid URL format
- HTTP/HTTPS protocol
Custom Code
- Length: 4-20 characters
- Characters: Alphanumeric only (
a-z,A-Z,0-9) - Must be unique
Expiration Date
- Must be ISO 8601 format
- Must be future date (not enforced at creation)
Error Responses
400 Bad Request
{
"error": "Custom code is already in use"
}404 Not Found
{
"error": "Short URL not found"
}410 Gone
{
"error": "Short URL has expired"
}Project Structure
url-shortener/
├── src/
│ ├── index.ts # Entry point
│ ├── routes.ts # Route handlers
│ ├── models.ts # TypeScript interfaces
│ ├── storage.ts # In-memory storage
│ ├── generator.ts # Short code generation
│ ├── validator.ts # Zod validation schemas
│ └── swagger.ts # OpenAPI config
├── package.json
├── tsconfig.json
├── .gitignore
├── README.md
└── PROJECT_SPEC.mdNotes
- Uses in-memory storage (data lost on restart)
- No authentication required
- Base URL configurable via
BASE_URLenv var - Port configurable via
PORTenv var (default: 3000)
License
MIT