How I Built a Serverless URL Shortener on AWS Using Terraform
Jorge Contreras
Posted on November 25, 2024
Hey there, fellow devs! π
Iβve been practicing on AWS as preparation for my certification exams. I wanted to share a project I worked on: a serverless URL shortener. Itβs a simple, real-world application that demonstrates the power of AWS services like Lambda, API Gateway, and DynamoDB, while also introducing Terraform for managing infrastructure as code.
By the end of this guide, youβll have a fully functional URL shortener that:
- Shortens long URLs with a POST request.
- Redirects users to the original URL with a GET request.
- Uses AWS's serverless stack for scalability and low cost.
So, grab a coffee β and letβs get started!
AWS Services Used
- AWS Lambda: To execute the backend logic for shortening and redirecting URLs.
- Amazon DynamoDB: To store mappings between short and long URLs.
- Amazon API Gateway: To expose RESTful API endpoints.
- Terraform: To define and deploy the infrastructure as code.
Prerequisites
Before we start building, make sure you have the following tools installed and configured:
AWS Account:
Youβll need an AWS account to deploy resources.
AWS CLI:
Install the AWS Command Line Interface (CLI).
After installing, configure the CLI with your AWS credentials:
aws configure
Youβll be prompted to enter:
- Access Key ID
- Secret Access Key
- Default AWS Region (e.g., us-east-1)
- Output Format (leave as json)
Run this command to verify the configuration:
aws s3 ls
If it lists your S3 buckets (or none if you donβt have any), the
setup is complete.
Terraform:
Install Terraform (v1.5+ recommended).
Python:
Install Python 3.9 or higher to write the Lambda functions.
cURL or Postman:
These tools will help you test the API endpoints after deployment.
Step 1. Create the project structure:
project-root/
βββ application/
β βββ backend/
β β βββ lambda_create/
β β β βββ create_short_url.py
β β βββ lambda_get/
β β βββ get_original_url.py
βββ terraform/ # Terraform configuration files
βββ main.tf # Defines AWS resources
Step 2: Writing the Lambda Functions
AWS Lambda is a serverless compute service that lets you run code without managing servers. You simply write your code, upload it to Lambda, and AWS handles the restβlike provisioning, scaling, and running the infrastructure. In this section, we'll write the logic in python and package it as zip to be uploaded.
We will create two lambdas: one for creating the short url, and the other to get the original url back from a short url.
1. Create the CreateShortURL
Function
File: application/backend/lambda_create/create_short_url.py
import json
import boto3
import uuid
# Initialize DynamoDB client
dynamodb = boto3.client('dynamodb')
table_name = 'URLMapping' # Replace with your DynamoDB table name
def lambda_handler(event, context):
try:
# Parse the request body
body = json.loads(event.get('body', '{}'))
original_url = body.get('OriginalURL')
if not original_url:
return {
'statusCode': 400,
'body': json.dumps({'message': 'OriginalURL is required'})
}
# Generate a unique short URL identifier (6-character UUID)
short_url = str(uuid.uuid4())[:6]
# Store the mapping in DynamoDB
dynamodb.put_item(
TableName=table_name,
Item={
'ShortURL': {'S': short_url},
'OriginalURL': {'S': original_url}
}
)
# Return the short URL
return {
'statusCode': 200,
'body': json.dumps({'ShortURL': short_url})
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps({'message': 'Internal server error', 'error': str(e)})
}
2. Create the GetOriginalURL Function
File: application/backend/lambda_get/get_original_url.py
import json
import boto3
# Initialize DynamoDB client
dynamodb = boto3.client('dynamodb')
table_name = 'URLMapping' # Replace with your DynamoDB table name
def lambda_handler(event, context):
try:
# Get the short URL from the path parameter
short_url = event['pathParameters']['short_url']
# Query DynamoDB for the original URL
response = dynamodb.get_item(
TableName=table_name,
Key={'ShortURL': {'S': short_url}}
)
# Check if the item exists
if 'Item' not in response:
return {
'statusCode': 404,
'body': json.dumps({'message': 'Short URL not found'})
}
original_url = response['Item']['OriginalURL']['S']
# Return a 302 redirect to the original URL
return {
'statusCode': 302,
'headers': {
'Location': original_url
},
'body': ''
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps({'message': 'Internal server error', 'error': str(e)})
}
3. Package the lambda functions
cd application/backend/lambda_create
zip create_short_url.zip create_short_url.py
cd ../lambda_get
zip get_original_url.zip get_original_url.py
After packaging, ensure your project looks like this:
project-folder/
βββ application/
β βββ backend/ # Backend logic for API
β βββ lambda_create/ # CreateShortURL Lambda
β β βββ create_short_url.py
β β βββ create_short_url.zip
β βββ lambda_get/ # GetOriginalURL Lambda
β βββ get_original_url.py
β βββ get_original_url.zip
β
βββ terraform/ # Terraform configurations
βββ main.tf
Step 3: Define your Infrastructure as Code
In main.tf
, define the AWS resources. In this section, we define what resources will be created in the AWS cloud, from IAM roles and policies to Lambdas, DynamoDB and the API Gateway. It allows for managing your infrastructure as code, isn't that awesome?
The file is somewhat long so I just included a snippet here. You can find the whole contents of the file in my github repo
# DynamoDB Table
resource "aws_dynamodb_table" "url_mapping" {
name = "URLMapping"
hash_key = "short_url"
attribute {
name = "short_url"
type = "S"
}
billing_mode = "PAY_PER_REQUEST"
}
# IAM Role for Lambda
resource "aws_iam_role" "lambda_exec_role" {
name = "url_shortener_lambda_role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}]
})
}
# Lambda Functions
resource "aws_lambda_function" "create_short_url" {
filename = "${path.module}/../application/backend/lambda_create/create_short_url.zip"
function_name = "CreateShortURL"
role = aws_iam_role.lambda_exec_role.arn
handler = "create_short_url.lambda_handler"
runtime = "python3.9"
environment {
variables = {
TABLE_NAME = aws_dynamodb_table.url_mapping.name
}
}
}
Step 4: Deploy your infrastructure!
Run the following commands:
cd terraform
terraform init
terraform plan
terraform apply
Step 5. Test
1. Shorten a URL
Send a POST request:
curl -X POST -H "Content-Type: application/json" \
-d '{"OriginalURL": "https://www.example.com"}' \
https://<api-gateway-id>.execute-api.<region>.amazonaws.com/shorten
2. Redirect using the short URL
curl -I https://<api-gateway-id>.execute-api.<region>.amazonaws.com/<short_url>
Conclusion
And thatβs it! π Youβve built a fully functional URL shortener using AWS and Terraform. Checkout the full project here
Hereβs what we achieved:
- Learned how to set up AWS resources with Terraform.
- Created Lambda functions for shortening and redirecting URLs.
- Integrated everything with API Gateway and DynamoDB.
Stay tuned for more AWS articles! next, weβll explore securing APIs and adding custom domain names. Happy coding!
Posted on November 25, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.