Build a Serverless URL Shortener on AWS with Lambda, API Gateway and DynamoDB
Learn how to build a serverless URL shortener on AWS in about an hour. This beginner-friendly, step-by-step guide uses Lambda, API Gateway and DynamoDB, with least-privilege IAM permissions and no servers to manage. One-line version (if there’s a character limit): Build a serverless URL shortener with AWS Lambda, API Gateway and DynamoDB, step by step.
Introduction
When I started learning AWS, I kept reading about “serverless” without knowing what it looked like in practice. The best way to learn was to build something small but real. In this article I’ll walk through a URL shortener: you send it a long link, it gives you a short code, and visiting the short link redirects you to the original.
It’s a good first project because it touches three core AWS services and teaches ideas you’ll reuse everywhere: event-driven code, NoSQL data modeling, and least-privilege permissions.
Architecture
The flow has three parts:
- Amazon API Gateway receives HTTP requests.
- AWS Lambda runs the logic to create or look up a link.
- Amazon DynamoDB stores the mapping between short code and original URL.
There are two routes:
POST /linkstakes a long URL and returns a short codeGET /{code}looks up the code and redirects to the original URL
Step 1: Create the DynamoDB table
- Open the DynamoDB console and choose Create table.
- Name it
short-links. - Set the partition key to
code(type: String). - Choose On-demand capacity so you only pay for what you use.
Each item will look like
{ "code": "aB3xY9", "url": "https://example.com/some/very/long/path" }.Step 2: Write the Lambda function
Create a Lambda function named
url-shortener using the Python 3.12 runtime (or the latest Python runtime available). Under Configuration → Environment variables, add TABLE_NAME = short-links. Then paste in this code:python
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
import json
import os
import random
import string
import boto3
from botocore.exceptions import ClientError
table = boto3.resource("dynamodb").Table(os.environ["TABLE_NAME"])
def response(status, body):
return {
"statusCode": status,
"headers": {"Content-Type": "application/json"},
"body": json.dumps(body),
}
def handler(event, context):
method = event["requestContext"]["http"]["method"]
# Create a short link
if method == "POST":
body = json.loads(event.get("body") or "{}")
url = body.get("url", "")
if not url.startswith(("http://", "https://")):
return response(400, {"error": "Please provide a valid http(s) URL"})
code = "".join(random.choices(string.ascii_letters + string.digits, k=6))
try:
table.put_item(
Item={"code": code, "url": url},
ConditionExpression="attribute_not_exists(code)",
)
except ClientError as e:
if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
return response(500, {"error": "Code collision, please retry"})
raise
return response(201, {"code": code})
# Redirect from a short link
code = event.get("pathParameters", {}).get("code")
item = table.get_item(Key={"code": code}).get("Item")
if not item:
return response(404, {"error": "Link not found"})
return {"statusCode": 302, "headers": {"Location": item["url"]}}A few things worth noticing:
- The
ConditionExpressionstops a new link from overwriting an existing code, which is a small but important safety check. - Validating that the URL starts with
http://orhttps://prevents junk and unsafe schemes likejavascript:from being stored.
Step 3: Give Lambda permission to use DynamoDB
By default, your function can’t touch DynamoDB. Open the function’s execution role in IAM and attach an inline policy that grants only what’s needed:
json
1
2
3
4
5
6
7
8
9
10
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["dynamodb:PutItem", "dynamodb:GetItem"],
"Resource": "arn:aws:dynamodb:REGION:ACCOUNT_ID:table/short-links"
}
]
}Replace
REGION and ACCOUNT_ID with your own values. This is the principle of least privilege: the function can only read and write one table, nothing more.Step 4: Expose it with API Gateway
- In API Gateway, create an HTTP API.
- Add a Lambda integration pointing to
url-shortener. - Create two routes:
POST /linksandGET /{code}. - Deploy with the default stage and copy the Invoke URL.
Step 5: Test it
Create a short link:
bash
1
2
3
curl -X POST https://YOUR_API_ID.execute-api.REGION.amazonaws.com/links \
-H "Content-Type: application/json" \
-d '{"url": "https://aws.amazon.com/builders/"}'You’ll get back something like
{"code": "aB3xY9"}. Open https://YOUR_API_ID.execute-api.REGION.amazonaws.com/aB3xY9 in your browser, and you should land on the original page.Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article