Project Overview
Estimated Time : 2-3 hours | Difficulty : Intermediate | Cost : ~$5/month for testing
In this hands-on project, you’ll build a complete serverless URL shortener service. This is a common system design interview question and demonstrates key serverless patterns.
What You’ll Build:
REST API with API Gateway
Lambda functions for business logic
DynamoDB for data persistence
CloudFront for caching and performance
Complete observability with CloudWatch and X-Ray
Skills Demonstrated:
Serverless architecture design
DynamoDB data modeling
API design and implementation
Cost optimization
Production-ready practices
Architecture Overview
Let’s design a serverless web application for a URL shortener service.
Requirements
Handle 10,000+ requests per second
Sub-100ms latency
99.9% availability
Pay only for actual usage
Auto-scaling without management
High-Level Architecture
┌────────────────────────────────────────────────────────────────────┐
│ Serverless URL Shortener │
├────────────────────────────────────────────────────────────────────┤
│ │
│ User │
│ │ │
│ │ 1. POST /shorten │
│ │ 2. GET /abc123 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ CloudFront │ │
│ │ (CDN + Edge Caching) │ │
│ └─────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ API Gateway │ │
│ │ (REST API + Throttling) │ │
│ └─────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ Lambda: Create │ │ Lambda: │ │ Lambda: │ │
│ │ Short URL │ │ Redirect │ │ Analytics │ │
│ └────────┬────────┘ └──────┬──────┘ └────────┬────────┘ │
│ │ │ │ │
│ └─────────────────┼──────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ DynamoDB │ │
│ │ (URL Mappings Table) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────┘
Implementation
1. DynamoDB Table Design
# Table: url-shortener
# Partition Key: short_code (String)
# No Sort Key needed
table_schema = {
"TableName" : "url-shortener" ,
"KeySchema" : [
{ "AttributeName" : "short_code" , "KeyType" : "HASH" }
],
"AttributeDefinitions" : [
{ "AttributeName" : "short_code" , "AttributeType" : "S" }
],
"BillingMode" : "PAY_PER_REQUEST" , # Auto-scaling
"TimeToLiveSpecification" : {
"AttributeName" : "expires_at" ,
"Enabled" : True
}
}
# Item structure
item = {
"short_code" : "abc123" ,
"long_url" : "https://example.com/very/long/url/path" ,
"created_at" : "2024-01-15T10:00:00Z" ,
"expires_at" : 1705312800 , # TTL epoch
"click_count" : 0 ,
"user_id" : "user_456" # Optional
}
2. Lambda: Create Short URL
import json
import boto3
import hashlib
import time
dynamodb = boto3.resource( 'dynamodb' )
table = dynamodb.Table( 'url-shortener' )
def generate_short_code ( url : str ) -> str :
"""Generate 6-character short code from URL hash."""
hash_object = hashlib.sha256( f " { url }{ time.time() } " .encode())
return hash_object.hexdigest()[: 6 ]
def lambda_handler ( event , context ):
try :
body = json.loads(event[ 'body' ])
long_url = body[ 'url' ]
# Validate URL
if not long_url.startswith(( 'http://' , 'https://' )):
return {
'statusCode' : 400 ,
'body' : json.dumps({ 'error' : 'Invalid URL' })
}
# Generate short code
short_code = generate_short_code(long_url)
# Store in DynamoDB
table.put_item( Item = {
'short_code' : short_code,
'long_url' : long_url,
'created_at' : int (time.time()),
'click_count' : 0
})
short_url = f "https://short.ly/ { short_code } "
return {
'statusCode' : 201 ,
'headers' : { 'Content-Type' : 'application/json' },
'body' : json.dumps({
'short_url' : short_url,
'short_code' : short_code
})
}
except Exception as e:
return {
'statusCode' : 500 ,
'body' : json.dumps({ 'error' : str (e)})
}
3. Lambda: Redirect
import json
import boto3
dynamodb = boto3.resource( 'dynamodb' )
table = dynamodb.Table( 'url-shortener' )
def lambda_handler ( event , context ):
short_code = event[ 'pathParameters' ][ 'code' ]
# Get URL from DynamoDB
response = table.get_item( Key = { 'short_code' : short_code})
if 'Item' not in response:
return {
'statusCode' : 404 ,
'body' : json.dumps({ 'error' : 'URL not found' })
}
long_url = response[ 'Item' ][ 'long_url' ]
# Increment click count (async would be better)
table.update_item(
Key = { 'short_code' : short_code},
UpdateExpression = 'ADD click_count :inc' ,
ExpressionAttributeValues = { ':inc' : 1 }
)
# Return 301 redirect
return {
'statusCode' : 301 ,
'headers' : {
'Location' : long_url,
'Cache-Control' : 'max-age=300' # Cache redirect for 5 min
}
}
4. API Gateway Configuration
# serverless.yml (Serverless Framework)
service : url-shortener
provider :
name : aws
runtime : python3.11
region : us-east-1
iam :
role :
statements :
- Effect : Allow
Action :
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
Resource : !GetAtt UrlTable.Arn
functions :
createUrl :
handler : handlers.create_url
events :
- http :
path : /shorten
method : post
cors : true
redirect :
handler : handlers.redirect
events :
- http :
path : /{code}
method : get
resources :
Resources :
UrlTable :
Type : AWS::DynamoDB::Table
Properties :
TableName : url-shortener
BillingMode : PAY_PER_REQUEST
AttributeDefinitions :
- AttributeName : short_code
AttributeType : S
KeySchema :
- AttributeName : short_code
KeyType : HASH
Scaling Considerations
API Gateway Limits
┌────────────────────────────────────────────────────────────────┐
│ API Gateway Throttling │
├────────────────────────────────────────────────────────────────┤
│ │
│ Default Limits (per region): │
│ • 10,000 requests/second │
│ • 5,000 concurrent requests │
│ │
│ Can request increase for high-traffic apps │
│ │
│ Throttling Strategies: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 1. Usage Plans - Per-API key limits │ │
│ │ 2. Stage throttling - Per-stage limits │ │
│ │ 3. Method throttling - Per-endpoint limits │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
Lambda Concurrency
# Lambda scales automatically
# Default: 1000 concurrent executions per region
# Reserved concurrency - Guarantee capacity
# Provisioned concurrency - Eliminate cold starts
concurrency_config = {
"redirect_function" : {
"reserved_concurrency" : 500 , # Guarantee 500 concurrent
"provisioned_concurrency" : 100 # Pre-warmed instances
}
}
DynamoDB Capacity
┌────────────────────────────────────────────────────────────────┐
│ DynamoDB Scaling │
├────────────────────────────────────────────────────────────────┤
│ │
│ On-Demand Mode: │
│ • Auto-scales instantly │
│ • Pay per request │
│ • Best for unpredictable traffic │
│ │
│ Provisioned Mode: │
│ • Set RCU/WCU capacity │
│ • Auto-scaling available │
│ • Better for predictable traffic │
│ │
│ DAX (DynamoDB Accelerator): │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Lambda │ ───► │ DAX │ ───► │ DynamoDB │ │
│ └──────────┘ │ (cache) │ └──────────┘ │
│ └──────────┘ │
│ • Microsecond latency │
│ • 10x read performance │
│ │
└────────────────────────────────────────────────────────────────┘
Cost Analysis
# Monthly cost estimate for 10M requests/month
costs = {
"api_gateway" : {
"requests" : 10_000_000 ,
"cost_per_million" : 3.50 ,
"total" : 35.00 # $35
},
"lambda" : {
"requests" : 10_000_000 ,
"avg_duration_ms" : 50 ,
"memory_mb" : 128 ,
"gb_seconds" : 10_000_000 * 0.050 * ( 128 / 1024 ),
"compute_cost" : 6.25 , # ~$0.0000166667 per GB-second
"request_cost" : 2.00 , # $0.20 per million
"total" : 8.25 # $8.25
},
"dynamodb" : {
"writes" : 1_000_000 , # 10% create new
"reads" : 10_000_000 ,
"write_cost" : 1.25 , # $1.25 per million WRU
"read_cost" : 0.25 , # $0.25 per million RRU
"storage_gb" : 1 ,
"storage_cost" : 0.25 ,
"total" : 1.75 # $1.75
},
"cloudfront" : {
"requests" : 10_000_000 ,
"data_transfer_gb" : 10 ,
"total" : 10.00 # ~$10
},
"total_monthly" : 55.00 # ~$55/month for 10M requests
}
Monitoring & Observability
┌────────────────────────────────────────────────────────────────┐
│ Monitoring Stack │
├────────────────────────────────────────────────────────────────┤
│ │
│ CloudWatch Metrics │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ • Lambda: Invocations, Duration, Errors, Throttles │ │
│ │ • API GW: Count, Latency, 4XX, 5XX │ │
│ │ • DynamoDB: ConsumedRCU, ConsumedWCU, Throttles │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ CloudWatch Alarms │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ • Lambda errors > 1% → Alert │ │
│ │ • API Gateway 5XX > 0.1% → Alert │ │
│ │ • DynamoDB throttling → Alert │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ X-Ray Tracing │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ API GW → Lambda → DynamoDB │ │
│ │ End-to-end latency visualization │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
Security Considerations
API Gateway
WAF integration
API keys for rate limiting
Request validation
Lambda
Least privilege IAM
VPC for private resources
Secrets in Secrets Manager
DynamoDB
Encryption at rest
IAM for access control
VPC endpoints
CloudFront
HTTPS only
Geo-restrictions if needed
Origin Access Identity
Key Takeaway : Serverless architectures eliminate server management, scale automatically, and cost effectively for variable workloads. Start simple and add complexity (caching, analytics) as needed.
🎯 Interview Questions for This Case Study
Q1: Why use serverless for a URL shortener?
Advantages:
Variable traffic (pay for actual usage)
No server management
Auto-scales from 0 to millions
Quick time to market
Trade-offs:
Cold start latency (mitigate with provisioned concurrency)
15-minute function limit (not an issue here)
Vendor lock-in
When NOT serverless:
Predictable, constant high traffic (EC2 cheaper)
Long-running processes
WebSocket-heavy (API Gateway WS works but pricey)
Q2: How would you handle 1 million concurrent users?
Scaling strategy:
CloudFront : Cache redirects at edge
301 with Cache-Control: max-age=300
80%+ cache hit ratio
API Gateway : Request higher limits
Default 10K/sec is usually enough
Lambda : Reserved concurrency
Set to expected peak (e.g., 5000)
DynamoDB : On-demand mode handles spikes
Add DAX for microsecond reads
Monitoring : Alarms before hitting limits
Q3: How would you prevent abuse?
Defense layers:
Rate limiting : API Gateway usage plans
usagePlan :
quota :
limit : 1000
period : DAY
throttle :
burstLimit : 50
rateLimit : 10
WAF : Block malicious IPs, SQL injection
CAPTCHA : For create endpoint
URL validation : Block malicious targets
User authentication : Cognito for premium features
Q4: How would you add analytics?
Async analytics pipeline: Lambda → Kinesis Data Firehose → S3 → Athena
Log click events to Kinesis (async, non-blocking)
Firehose batches to S3 every 60 seconds
Query with Athena for dashboards
Metrics to track:
Click count per URL
Geographic distribution
Referrer analysis
Time-based patterns
Q5: What's the total cost at scale?
Cost breakdown at 100M requests/month: Service Cost API Gateway $350 Lambda $83 DynamoDB $35 CloudFront $85 Total ~$550/month
Optimization opportunities:
CloudFront caching (reduces Lambda calls 80%)
Reserved concurrency (predictable billing)
DynamoDB provisioned (if traffic predictable)
🧪 Hands-On Implementation Steps
Create DynamoDB Table
Create table with on-demand billing and TTL enabled
Create Lambda Functions
Deploy create and redirect functions with proper IAM roles
Set Up API Gateway
Create REST API with two endpoints, deploy to prod stage
Add CloudFront Distribution
Point to API Gateway, configure caching
Configure Monitoring
Set up CloudWatch alarms and X-Ray tracing
Test Load
Use Artillery or hey to test performance
Course Complete! 🎉
Congratulations on completing the AWS Cloud Fundamentals course! You’ve learned:
✅ AWS core concepts and regions
✅ Compute services (EC2, Lambda, ECS)
✅ Storage and databases (S3, RDS, DynamoDB)
✅ Networking (VPC, security groups, load balancers)
✅ Security (IAM, KMS, encryption)
✅ Well-Architected Framework
✅ Real-world serverless architecture
Next Steps:
Practice with hands-on labs
Build your own projects
Prepare for AWS certification
Review interview questions regularly
Return to Course Overview Review the complete curriculum and resources