Table of contents
Introduction
In this blog post, I’ll share how I tackled the AWS Cloud Resume Challenge, focusing on building a dynamic backend using API Gateway, AWS Lambda, DynamoDB, and Simple Email Service (SES). The goal was to create a visitor counter that increments only for unique IP addresses, stores data in DynamoDB, and sends a milestone email when the counter hits 100 visitors.
Key AWS Services Used
API Gateway: Exposed a REST API to interact with my backend services. a fully managed service that makes it easy for developers to create, publish, maintain, monitor, and secure APIs at any scale. APIs act as the "front door" for applications to access data, business logic, or functionality from your backend services.
AWS Lambda: Hosted the business logic to process requests. a serverless computing service that lets developers run code without managing servers. It's a Function as a Service (FaaS) that automatically manages computing resources based on events
DynamoDB: Stored visitor count data. a database service from Amazon Web Services (AWS) that stores data in tables and supports key-value and document data structures.
Simple Email Service (SES): Sent milestone email notifications. a cloud-based email service provider that can integrate into any application for high-volume email automation
Architecture Overview
Step 1: Setting Up DynamoDB
Create DynamoDB Table
Open the AWS Management Console.
Navigate to DynamoDB > Tables.
Click Create Table:
Table Name:
VisitorCounter
Partition Key:
id
(String)
Click Create.
Initialize Table Data
Add an item manually via the console:
id
:visitor_count
count
:0
(Number)visitor_ips
:[]
(List of Strings)
Step 2: Create Lambda Function
Set Up the Lambda Function
Navigate to AWS Lambda and click Create Function.
Select Author from scratch:
Function Name:
VisitorCounterFunction
Runtime: Python 3.x
Role: Create a new role with basic Lambda permissions.
Click Create Function.
import boto3
import json
from decimal import Decimal
# Initialize DynamoDB and SES clients
dynamodb = boto3.resource('dynamodb')
ses = boto3.client('ses', region_name='us-east-1') # Update with your SES region
table = dynamodb.Table('VisitorCounter')
# Helper function to convert Decimal to native Python types
def decimal_to_native(obj):
if isinstance(obj, list):
return [decimal_to_native(i) for i in obj]
elif isinstance(obj, dict):
return {k: decimal_to_native(v) for k, v in obj.items()}
elif isinstance(obj, Decimal):
return int(obj) if obj % 1 == 0 else float(obj)
else:
return obj
def send_email(visitor_count):
subject = "Milestone Reached: 100 Visitors!"
body = f"Congratulations! Your website has reached {visitor_count} visitors."
sender = "your_verified_email@example.com" # Replace with your verified email
recipient = "your_verified_email@example.com" # Replace with your verified email
# Send email using SES
response = ses.send_email(
Source=sender,
Destination={'ToAddresses': [recipient]},
Message={
'Subject': {'Data': subject},
'Body': {'Text': {'Data': body}}
}
)
print("Email sent! Message ID:", response['MessageId'])
def lambda_handler(event, context):
try:
# Get the IP address from the request context
ip_address = event.get('requestContext', {}).get('identity', {}).get('sourceIp', 'unknown')
print(f"IP Address: {ip_address}")
# Retrieve the visitor count and IP list from DynamoDB
response = table.get_item(Key={'id': 'visitor_count'})
if 'Item' not in response:
# If no item exists, initialize with count 1 and the current IP
visitor_count = 1
visitor_ips = [ip_address]
table.put_item(Item={
'id': 'visitor_count',
'count': Decimal(visitor_count),
'visitor_ips': visitor_ips
})
else:
# Retrieve the existing data
visitor_count = response['Item']['count']
visitor_ips = response['Item'].get('visitor_ips', [])
# Ensure visitor_ips is a list
if not isinstance(visitor_ips, list):
visitor_ips = []
# Increment the count only if the IP is new
if ip_address != 'unknown' and ip_address not in visitor_ips:
visitor_count += 1
visitor_ips.append(ip_address)
# Update DynamoDB with the new count and IP list
table.update_item(
Key={'id': 'visitor_count'},
UpdateExpression='SET #cnt = :val1, visitor_ips = :val2',
ExpressionAttributeNames={'#cnt': 'count'},
ExpressionAttributeValues={
':val1': Decimal(visitor_count),
':val2': visitor_ips
}
)
print(f"Visitor count incremented. New count: {visitor_count}")
# Send an email if visitor count reaches 100
if visitor_count == 100:
send_email(visitor_count)
else:
print(f"IP {ip_address} already exists. Count remains unchanged.")
# Return the visitor count and IP list in JSON format
return {
'statusCode': 200,
'body': json.dumps({
'visitorCount': decimal_to_native(visitor_count),
'visitorIps': decimal_to_native(visitor_ips)
})
}
except Exception as e:
print(f"Error: {e}")
return {
'statusCode': 500,
'body': json.dumps({'error': str(e)})
}
Step 3: Create a REST API in API Gateway
Navigate to API Gateway
Go to the AWS Management Console and select API Gateway.Create a New REST API
Click Create API.
Select REST API.
Choose Build.
Set Up the API Details
API Name:
VisitorCounterAPI
Description:
API for tracking unique visitor IPs and updating the count in DynamoDB
Endpoint Type: Regional (default)
Create a Resource and Method
Add a Resource
In the Resources section, click Actions > Create Resource.
Resource Name:
visitors
Resource Path:
/visitors
Click Create Resource.
Add a GET Method
Select the
/visitors
resource.Click Actions > Create Method.
Choose GET .
Integrate with Lambda Function
Select Lambda Function as the integration type.
Check Use Lambda Proxy Integration.
In the Lambda Function field, enter your function name (e.g.,
VisitorCounterFunction
).Click Save.
Confirm the permission prompt to allow API Gateway to invoke your Lambda function.
Deploy the API
Create a Deployment Stage
Click Actions > Deploy API.
Deployment Stage: Create a new stage (e.g.,
prod
).Click Deploy.
Get the Endpoint URL
- After deployment, you’ll see the Invoke URL (e.g.,
https://<your-api-id>.execute-api.<region>.
amazonaws.com/prod/visitors
).
- After deployment, you’ll see the Invoke URL (e.g.,
Step 4: Configuring SES
Verified my email address in Amazon SES.
Set up the Lambda function to send emails when the visitor count reaches 100.
Update IAM Policy for SES Permissions:
Ensure the IAM role for your Lambda function has the correct permissions to use SES with a configuration set. Update the policy as follows:
Go to the IAM Console.
Locate the role (
visitorCounter-role-js6y5f91
) used by your Lambda function.Edit the policy and add this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ses:SendEmail",
"Resource": "arn:aws:ses:ap-southeast-1:183631338445:identity/your_verified_email.com"
},
{
"Effect": "Allow",
"Action": "ses:SendEmail",
"Resource": "arn:aws:ses:ap-southeast-1:183631338445:configuration-set/my-first-configuration-set"
}
]
}
Results
Successfully built a visitor counter that tracks unique IP addresses.
The visitor count updates in real-time and sends an email when milestones are reached.
The final JSON response looks like this:
jsonCopy code{
"visitorCount": 100,
"visitorIps": ["192.168.1.1", "203.0.113.5", "192.168.0.100"]
}
You can implement a JavaScript code on your web resume that triggers the API each time a user visits the site. This API call will invoke the Lambda function, which will then update the DynamoDB table accordingly.
Conclusion and Next Steps
This project was a rewarding experience that allowed me to work with multiple AWS services. My next goal is to improve the solution by:
Adding a CI/CD pipeline
Cloudformation