How I Built a Serverless URL Shortener on AWS Using Terraform

jorgecontreras

Jorge Contreras

Posted on November 25, 2024

How I Built a Serverless URL Shortener on AWS Using Terraform

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

  1. AWS Lambda: To execute the backend logic for shortening and redirecting URLs.
  2. Amazon DynamoDB: To store mappings between short and long URLs.
  3. Amazon API Gateway: To expose RESTful API endpoints.
  4. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)})
        }
Enter fullscreen mode Exit fullscreen mode

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)})
        }

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Deploy your infrastructure!

Run the following commands:

cd terraform
terraform init
terraform plan
terraform apply
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

2. Redirect using the short URL

curl -I https://<api-gateway-id>.execute-api.<region>.amazonaws.com/<short_url>
Enter fullscreen mode Exit fullscreen mode

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!

πŸ’– πŸ’ͺ πŸ™… 🚩
jorgecontreras
Jorge Contreras

Posted on November 25, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related