Select your cookie preferences

We use essential cookies and similar tools that are necessary to provide our site and services. We use performance cookies to collect anonymous statistics, so we can understand how customers use our site and make improvements. Essential cookies cannot be deactivated, but you can choose “Customize” or “Decline” to decline performance cookies.

If you agree, AWS and approved third parties will also use cookies to provide useful site features, remember your preferences, and display relevant content, including relevant advertising. To accept or decline all non-essential cookies, choose “Accept” or “Decline.” To make more detailed choices, choose “Customize.”

AWS Logo
Menu
Building a Serverless URLShortener

Building a Serverless URLShortener

Sharing long URLs can be a hassle. URL shorteners create unique, shareable links. In this blog, we'll build a serverless URL shortener.

Akash Ninave
Amazon Employee
Published Feb 23, 2025
Title: Building a Serverless URL Shortener with AWS Lambda, API Gateway, and DynamoDB
Have you ever needed to share a long, unwieldy URL and wished there was an easy way to shorten it? URL shorteners are handy tools that generate a short, unique alias for a long URL, making it easier to share links across different platforms. In this blog post, we'll explore how to build a serverless URL shortener
The Serverless Approach Going serverless means you don't have to worry about provisioning or managing any servers. Instead, you build and run your application using cloud services that automatically scale based on demand. For our URL shortener, we'll be using:
  • AWS Lambda to run our URL shortening and redirection logic
  • Amazon API Gateway to expose HTTP endpoints for our Lambda functions
  • Amazon DynamoDB as a NoSQL database to store the URL mappings
  • Terraform for provisioning all the AWS resources
The Architecture Here's a high-level overview of how our serverless URL shortener will work:
  1. A client sends a POST request to the API Gateway endpoint with the long URL they want to shorten.
  2. API Gateway invokes the "create URL" Lambda function.
  3. The Lambda function generates a unique short ID, stores the mapping (short ID -> long URL) in DynamoDB, and returns the short ID.
  4. To access the original URL, the client sends a GET request to the API Gateway endpoint with the short ID.
  5. API Gateway invokes the "get URL" Lambda function.
  6. The Lambda function looks up the short ID in DynamoDB and returns an HTTP 301 redirect to the original long URL.
Sounds simple enough, right? Let's dive into the implementation details.
The Terraform Setup We'll be using Terraform to provision all the required AWS resources for our URL shortener. The main.tf file defines the core infrastructure:
  • A DynamoDB table to store the URL mappings
  • An IAM role and policy to grant Lambda functions access to DynamoDB
  • Two Lambda functions: one for creating short URLs, another for retrieving the original URLs
  • An API Gateway HTTP API with routes and integrations mapped to the Lambda functions
The Lambda Functions The URL shortening logic lives in the create_url.py Lambda function:
  1. It parses the request body to get the original long URL.
  2. Generates a unique 8-character short ID by hashing the long URL and current timestamp.
  3. Stores the short ID -> long URL mapping in DynamoDB.
  4. Returns the short ID in the response.
The get_url.py Lambda function handles URL redirection:
  1. Extracts the short ID from the request path parameters.
  2. Queries DynamoDB for the corresponding long URL.
  3. If found, returns an HTTP 301 redirect to the long URL.
  4. If not found, returns a 404 error.
Using the URL Shortener After deploying the Terraform configuration, you'll get an API Gateway endpoint URL. To create a new shortened URL, send a POST request with the long URL in the request body:
curl -X POST <api_endpoint>/url \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/very-long-url"}'
This will return a JSON response with the generated short ID, e.g., {"short_id": "a1b2c3d4"}.
To access the original URL, simply send a GET request with the short ID:
curl -L <api_endpoint>/a1b2c3d4
The -L flag in curl follows the HTTP 301 redirect to the long URL.
So that was the high level explainantion, Now let us get to implementation with all necessary detaiils.
Lets leverage LLMS and AI for this purpose to make the job easy.
Let us use Amazon Q to help us with the problem statement.
Image not found
amazon q

Image not found
q
1. Here you can see Q has given us folder structure as well as code now lets copy that code into our files I am pasting code for each filr
a. tf
b. tf
c. tf
d. tf
e. Lambda
i. Create-url.py
ii. Get-url.py
2. Main.tf
Image not found
main code
Okay so here is the entire code snippet :-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
provider "aws" {
region = var.aws_region
}

# Create zip files for Lambda functions
data "archive_file" "create_url_lambda" {
type = "zip"
source_file = "${path.module}/lambda/create_url.py"
output_path = "${path.module}/lambda/create_url.zip"
}

data "archive_file" "get_url_lambda" {
type = "zip"
source_file = "${path.module}/lambda/get_url.py"
output_path = "${path.module}/lambda/get_url.zip"
}

# DynamoDB Table
resource "aws_dynamodb_table" "url_table" {
name = "${var.project_name}-urls-${var.environment}"
billing_mode = "PAY_PER_REQUEST"
hash_key = "short_id"
attribute {
name = "short_id"
type = "S"
}
tags = {
Environment = var.environment
Project = var.project_name
}
}

# IAM Role for Lambda
resource "aws_iam_role" "lambda_role" {
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Action = "sts:AssumeRole",
Effect = "Allow",
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}

# IAM Policy for Lambda to access DynamoDB
resource "aws_iam_role_policy" "lambda_dynamodb_policy" {
name = "${var.project_name}-lambda-dynamodb-policy-${var.environment}"
role = aws_iam_role.lambda_role.id
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Action = ["dynamodb:PutItem", "dynamodb:GetItem"],
Resource = aws_dynamodb_table.url_table.arn
}
]
})
}

# Lambda basic execution role
resource "aws_iam_role_policy_attachment" "lambda_basic" {
name = "${var.project_name}-lambda-role-${var.environment}"
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
role = aws_iam_role.lambda_role.name
}

# Lambda Functions
resource "aws_lambda_function" "create_url" {
filename = data.archive_file.create_url_lambda.output_path
function_name = "${var.project_name}-create-url-${var.environment}"
role = aws_iam_role.lambda_role.arn
handler = "create_url.lambda_handler"
runtime = "python3.9"
environment {
variables = {
DYNAMODB_TABLE = aws_dynamodb_table.url_table.name
}
}
tags = {
Environment = var.environment
Project = var.project_name
}
}

resource "aws_lambda_function" "get_url" {
filename = data.archive_file.get_url_lambda.output_path
function_name = "${var.project_name}-get-url-${var.environment}"
role = aws_iam_role.lambda_role.arn
handler = "get_url.lambda_handler"
runtime = "python3.9"
environment {
variables = {
DYNAMODB_TABLE = aws_dynamodb_table.url_table.name
}
}
tags = {
Environment = var.environment
Project = var.project_name
}
}

# API Gateway
resource "aws_apigatewayv2_api" "url_shortener" {
name = "${var.project_name}-api-${var.environment}"
protocol_type = "HTTP"
cors_configuration {
allow_origins = ["*"]
allow_methods = ["GET", "POST", "OPTIONS"]
allow_headers = ["content-type"]
}
}

resource "aws_apigatewayv2_stage" "url_shortener" {
api_id = aws_apigatewayv2_api.url_shortener.id
name = var.environment
auto_deploy = true
}

# API Gateway Integrations and Routes
resource "aws_apigatewayv2_integration" "create_url" {
api_id = aws_apigatewayv2_api.url_shortener.id
integration_type = "AWS_PROXY"
integration_method = "POST"
integration_uri = aws_lambda_function.create_url.invoke_arn
}

resource "aws_apigatewayv2_integration" "get_url" {
api_id = aws_apigatewayv2_api.url_shortener.id
integration_type = "AWS_PROXY"
integration_method = "POST"
integration_uri = aws_lambda_function.get_url.invoke_arn
}

resource "aws_apigatewayv2_route" "create_url" {
api_id = aws_apigatewayv2_api.url_shortener.id
route_key = "POST /url"
target = "integrations/${aws_apigatewayv2_integration.create_url.id}"
}

resource "aws_apigatewayv2_route" "get_url" {
api_id = aws_apigatewayv2_api.url_shortener.id
route_key = "GET /{shortId}"
target = "integrations/${aws_apigatewayv2_integration.get_url.id}"
}

# Lambda permissions for API Gateway
resource "aws_lambda_permission" "create_url" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.create_url.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.url_shortener.execution_arn}/*/*"
}

resource "aws_lambda_permission" "get_url" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.get_url.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.url_shortener.execution_arn}/*/*"
}


Explanation of redirect_url.py (Lambda Function for URL Redirection) :-

  1. Imports Required Libraries
    - json : Handles JSON response
    - os : Fetches environment variables (DynamoDB table name).
    - boto3 : AWS SDK for Python to interact with DynamoDB.
    - Key : Helps query DynamoDB items.
  2. Initialize DynamoDB Table
    - Reads DYNAMODB_TABLE from environment variables.
    - Connects to the DynamoDB table.
  3. Lambda Function ( lambda_handler )
    - Extracts short_id from the URL path parameters.
    - Queries DynamoDB for the corresponding long_url .
    - If found, returns an HTTP 301 redirect to long_url .
    - If not found, returns HTTP 404.
    - If an error occurs, returns HTTP 500 with the error message.

Example Workflow:-
📌 Request:-
GET /a1b2c3d4

📌 Response (Redirect to long URL):-
HTTP/1.1 301 Moved Permanently Location:
https://example.com/very-long-url


📌 If short_id not found:
{ "error": "URL not found" }

This function enables URL redirection by mapping short links to long URLs stored in DynamoDB.

Image not found
function

we paste all code with respect to their files now we can run to process terraform commands
1
2
3
terraform init
terraform plan
terraform apply
Image not found
teraform output
Image not found
teraform2
Now our infra is ready you can see api endpoint below now run following command to shorten url. To create a new shortened URL, use the POST endpoint. For example:
1
2
3
curl -X POST <api_endpoint>/url \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com"}'
This will return a response like:-
{ "short_id": "a1b2c3d4" }
Image not found
url response
To use the shortened URL, simply access:-
1
curl -L <api_endpoint>/a1b2c3d4
Image not found
url2
note :- (Replace a1b2c3d4 with the short_id you received)
This will redirect you to your original URL (https://example.com in this example).
Some important notes:-
The -L flag in curl follows the redirect The API endpoint will look something like:
https://abc123def.execute-api.useast-1.amazonaws.com/prod
Make sure to keep the API endpoint URL for future use
The shortened URLs are stored in DynamoDB and will persist until deleted.
Wrapping Up In this blog post, we explored how to build a serverless URL shortener using AWS services like Lambda, API Gateway, and DynamoDB. By leveraging serverless architectures, we can build and deploy applications without worrying about provisioning or managing servers.
The Terraform configuration makes it easy to provision all the required AWS resources, while the Python code running on Lambda handles the core URL shortening and redirection logic. With just a few API calls, users can shorten long URLs and access the original URLs through short, easy-to-share links.
This is just one example of how serverless architectures can simplify application development and deployment. As cloud services continue to evolve, we can expect to see more innovative use cases for serverless computing.
 

Any opinions in this post are those of the individual author and may not reflect the opinions of AWS.

Comments

Log in to comment