AWS Cloud Resume Challenge: API Gateway AWS Lambda DynamoDB and SES

AWS Cloud Resume Challenge: API Gateway AWS Lambda DynamoDB and SES

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

  1. Create DynamoDB Table

    • Open the AWS Management Console.

    • Navigate to DynamoDB > Tables.

    • Click Create Table:

      • Table Name: VisitorCounter

      • Partition Key: id (String)

    • Click Create.

  2. 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

  1. 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

  1. Navigate to API Gateway
    Go to the AWS Management Console and select API Gateway.

  2. Create a New REST API

    • Click Create API.

    • Select REST API.

    • Choose Build.

  3. 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

  1. Add a Resource

    • In the Resources section, click Actions > Create Resource.

    • Resource Name: visitors

    • Resource Path: /visitors

    • Click Create Resource.

  2. Add a GET Method

    • Select the /visitors resource.

    • Click Actions > Create Method.

    • Choose GET .

  3. 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

  1. Create a Deployment Stage

    • Click Actions > Deploy API.

    • Deployment Stage: Create a new stage (e.g., prod).

    • Click Deploy.

  2. Get the Endpoint URL

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