Skip to content

mayankus/URL-Shortener

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

URL Shortener

A production-grade serverless URL shortener built with AWS Lambda, API Gateway, and DynamoDB. Features AI-powered smart short code generation using Google Gemini API.

Architecture

POST /shorten                    GET /{shortCode}
     │                                │
     ▼                                ▼
API Gateway                     API Gateway
     │                                │
     ▼                                ▼
ShortenUrlFunction              RedirectFunction
     │                                │
     ├── Gemini AI (smart code)       ▼
     ├── Random fallback         DynamoDB Lookup
     └── DynamoDB Save               │
                                      ▼
                                302 Redirect → Original URL

Key Highlights

Production-Grade Patterns

  • Atomic Collision Handling — DynamoDB conditional writes ensure two simultaneous requests never overwrite each other, eliminating race conditions without application-level locking
  • Graceful Degradation — AI code generation fails silently with automatic fallback to random codes, ensuring 100% uptime regardless of external API availability
  • Singleton Pattern — DynamoDB client initialised once per Lambda container lifecycle, not per request, reducing latency by ~200ms

AWS Architecture Decisions

  • PAY_PER_REQUEST billing — scales to zero cost at zero traffic, ideal for variable workloads
  • ARM64 (Graviton) — 20% cheaper and faster than x86 Lambda
  • DynamoDB TTL — automatic URL expiry without cron jobs or cleanup Lambda functions
  • GSI on userId + createdAt — pre-built index for user history feature, sorted chronologically, zero query cost at read time

AI Integration

  • Smart short codes — Gemini AI generates meaningful codes ("lambda-gs") instead of random strings ("x4ongp")
  • Timeout-bounded — AI calls capped at 2 seconds, protecting API response time SLA
  • Output validation — AI responses validated before use, rejecting truncated or malformed suggestions

AWS Services Used

Service Purpose
AWS Lambda Serverless compute for business logic
API Gateway HTTP endpoints routing
DynamoDB NoSQL database for URL mappings
DynamoDB TTL Automatic URL expiry
DynamoDB GSI Query URLs by user (future auth)

Key Design Patterns

  • Repository Pattern — all DynamoDB operations isolated in UrlRepository
  • Graceful Degradation — AI failure falls back to random code generation
  • Conditional Writes — atomic collision handling in DynamoDB
  • Singleton Pattern — DynamoDB client initialised once per Lambda instance
  • Environment-based Config — local vs production via environment variables

Prerequisites

Install these tools in order:

1. Homebrew (macOS package manager)

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

2. Java 21

brew install --cask temurin@21
java -version  # should show openjdk 21

3. Maven

brew install maven
mvn -version

4. AWS CLI

brew install awscli
aws --version

# Configure with dummy credentials for local development
aws configure
# AWS Access Key ID: test
# AWS Secret Access Key: test
# Default region name: us-east-1
# Default output format: json

5. AWS SAM CLI

brew tap aws/tap
brew install aws-sam-cli
sam --version

6. Docker Desktop

Download from: https://www.docker.com/products/docker-desktop/

  • Choose Apple Silicon for M1/M2/M3/M4 Macs
  • Open Docker Desktop and wait for "Engine running"
docker --version

7. IntelliJ IDEA Community Edition (recommended)

brew install --cask intellij-idea-ce

Project Structure

url-shortener/
├── ShortenUrlFunction/
│   ├── pom.xml                          # Maven dependencies
│   └── src/main/java/com/urlshortener/
│       ├── handler/
│       │   ├── ShortenUrlHandler.java   # POST /shorten Lambda
│       │   └── RedirectHandler.java     # GET /{shortCode} Lambda
│       ├── model/
│       │   └── UrlMapping.java          # DynamoDB table model
│       ├── repository/
│       │   └── UrlRepository.java       # All DynamoDB operations
│       └── util/
│           ├── ShortCodeGenerator.java  # Random code generation
│           └── AiCodeSuggester.java     # Gemini AI integration
├── events/                              # Sample Lambda events for testing
├── template.yaml                        # SAM/CloudFormation infrastructure
├── env.json                             # Local environment variables (gitignored)
├── start-local.sh                       # One-command local startup script
└── .gitignore

Local Development Setup

Step 1 — Clone and open project

git clone <your-repo-url>
cd url-shortener
open -a "IntelliJ IDEA CE" .

Step 2 — Create env.json (never commit this file)

Create env.json in the project root:

{
  "ShortenUrlFunction": {
    "DYNAMODB_ENDPOINT": "http://host.docker.internal:8000",
    "TABLE_NAME": "url-shortener",
    "DOMAIN_BASE": "http://localhost:3000",
    "GEMINI_API_KEY": "your-gemini-api-key-here"
  },
  "RedirectFunction": {
    "DYNAMODB_ENDPOINT": "http://host.docker.internal:8000",
    "TABLE_NAME": "url-shortener",
    "DOMAIN_BASE": "http://localhost:3000"
  }
}

Get your Gemini API key from: https://aistudio.google.com/apikey Use model: gemini-2.5-flash (free tier: 20 requests/day)

Step 3 — Start local environment

# Make script executable (first time only)
chmod +x start-local.sh

# Start DynamoDB Local + create table
./start-local.sh

Step 4 — Build and run

sam build
sam local start-api --env-vars env.json

API is now running at http://localhost:3000


API Reference

POST /shorten — Create Short URL

Request:

# Random code (AI-generated when possible)
curl -X POST http://localhost:3000/shorten \
  -H "Content-Type: application/json" \
  -d '{"url": "https://aws.amazon.com/lambda/getting-started"}'

# Custom alias
curl -X POST http://localhost:3000/shorten \
  -H "Content-Type: application/json" \
  -d '{"url": "https://github.com", "alias": "my-github"}'

Response (201 Created):

{
  "shortCode": "lambda-gs",
  "shortUrl": "http://localhost:3000/lambda-gs",
  "originalUrl": "https://aws.amazon.com/lambda/getting-started"
}

Error Responses:

Status Meaning
400 Missing URL, invalid alias format, or alias too short/long
409 Custom alias already taken
500 Internal error (retry)

GET /{shortCode} — Redirect

curl -v http://localhost:3000/lambda-gs
# Returns: HTTP 302 with Location: https://aws.amazon.com/lambda/getting-started

Error Responses:

Status Meaning
404 Short code not found or expired
500 Internal error

Alias Rules

  • Length: 3–30 characters
  • Allowed characters: letters (a-z, A-Z), numbers (0-9), hyphens (-), underscores (_)
  • Cannot start or end with a hyphen
  • Cannot contain consecutive hyphens

DynamoDB Table Design

Table: url-shortener

Attribute Type Description
shortCode String (PK) Unique short code / alias
originalUrl String Full destination URL
createdAt String ISO 8601 creation timestamp
userId String Owner (anonymous until auth added)
ttl Number Unix epoch expiry (DynamoDB TTL)

GSI: UserUrlsIndex

  • Partition Key: userId
  • Sort Key: createdAt
  • Purpose: query all URLs by user, sorted by date

AI Integration

Uses Google Gemini API (gemini-2.5-flash) to generate meaningful short codes:

Input:  https://aws.amazon.com/lambda/getting-started
Output: lambda-gs  (instead of random "x4ongp")

Fallback chain:

AI available + valid response → use AI code
AI unavailable / timeout      → random 6-char alphanumeric code
AI code already taken         → random 6-char alphanumeric code

AI is optional — the service works fully without a Gemini API key.


Troubleshooting

DynamoDB connection refused:

# Check if container is running
docker ps | grep dynamodb

# Restart if stopped
docker start dynamodb-local

# Recreate table if data was lost (in-memory storage resets on restart)
./start-local.sh

SAM env vars not loading:

  • Ensure all variables are declared in template.yaml Globals section
  • env.json only overrides declared variables, it cannot inject new ones

Java version mismatch error:

# Verify Java 21 is active
java -version

# If wrong version, set Java 21
export JAVA_HOME=$(/usr/libexec/java_home -v 21)
export PATH=$JAVA_HOME/bin:$PATH

Port 3000 already in use:

lsof -i :3000
kill -9 <PID>

Cost

Local development: $0 (all services run locally via Docker + SAM)

AWS deployment (when ready):

Service Free Tier Expected Monthly Cost
Lambda 1M requests/month ~$0
API Gateway 1M calls/month ~$0
DynamoDB 25GB + 25 WCU/RCU ~$0
Total ~$0

Security Notes

  • Never commit env.json — it contains API keys
  • In production, use AWS Secrets Manager for API keys
  • API Gateway + Lambda IAM roles follow least-privilege principle
  • DynamoDB access scoped to specific table only

Future Enhancements (Project Roadmap)

  • User authentication with AWS Cognito
  • View all URLs created by user (history)
  • Click analytics tracking
  • URL safety scanning
  • Custom domain support
  • Rate limiting per user

Releases

Packages

Contributors

Languages