DEV Community

alok-38
alok-38

Posted on

Tackling the cloud portfolio challenge -- Cloud Resume

I recently came across a Sub stack article titled “Build Your Cloud Portfolio: 10 Projects That Will Land You the Interview.” To put theory into practice, I decided to start with the first challenge: Level 1 – The Foundation (Beginner).

This foundational project focuses on core cloud services, static website hosting, and basic networking concepts—all essential building blocks for anyone starting out in cloud engineering. It was a natural place to begin, as it emphasizes understanding how cloud infrastructure works together rather than jumping straight into advanced tooling.

The Architecture: S3 (Frontend) → CloudFront/CDN → API Gateway → Lambda/Functions → DynamoDB.

1.Architecture Overview

We are building:

S3 (Frontend) → CloudFront/CDN → API Gateway → Lambda → DynamoDB

  • S3: Hosts the static frontend (HTML, CSS, JS).

  • CloudFront: CDN for caching and delivering frontend globally.

  • API Gateway: Exposes an HTTP POST route /visit.

  • Lambda: Runs serverless JS code to update visitor counts.

  • DynamoDB: Stores the visitor count.

2. Frontend Setup

My frontend/ folder contains:

  • index.html

  • main.js

  • style.css

  • assets/

  • Goal: Increment a visitor counter in the footer using JS calling the Lambda API.

  • Mistake: Initially, API_URL wasn’t defined in main.js, causing ReferenceError: API_URL is not defined.

  • Fix: Defined const API_URL = "<your-api-endpoint>/visit"; at the top of main.js.

3. Lambda Setup

  • Created Lambda function visitorCounter with Node.js 24.x runtime.

  • First Mistake: Using ES modules (import) caused SyntaxError: Cannot use import statement outside a module.

  • Fix: Ensure the Lambda handler is set to visitorCounter.handler and zipped properly.

  • Second Mistake: Lambda role didn’t have DynamoDB permissions, causing:

AccessDeniedException: ...not authorized to perform: dynamodb:UpdateItem...
Enter fullscreen mode Exit fullscreen mode
  • Fix: Added inline policy VisitorCounterDynamoDBAccess to the Lambda execution role.

  • Lambda now uses this updated code:

import { DynamoDBClient, UpdateItemCommand } from "@aws-sdk/client-dynamodb";

const client = new DynamoDBClient({ region: "us-east-1" });

export const handler = async () => {
    const params = {
        TableName: "VisitorCounter",
        Key: { counter: { S: "visits" } },
        UpdateExpression: "SET #v = if_not_exists(#v, :start) + :inc",
        ExpressionAttributeNames: { "#v": "value" },
        ExpressionAttributeValues: {
            ":start": { N: "0" },
            ":inc": { N: "1" }
        },
        ReturnValues: "UPDATED_NEW"
    };
    const command = new UpdateItemCommand(params);
    const result = await client.send(command);

    return {
        statusCode: 200,
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ count: result.Attributes.value.N })
    };
};
Enter fullscreen mode Exit fullscreen mode

4. API Gateway Setup

  • Created HTTP API VisitorCounterAPI with POST /visit route.

  • Attached Lambda integration to this route.

  • Added permission for API Gateway to invoke Lambda:

aws lambda add-permission \
  --function-name visitorCounter \
  --principal apigateway.amazonaws.com \
  --statement-id apigateway-invoke \
  --action lambda:InvokeFunction \
  --source-arn arn:aws:execute-api:us-east-1:579747663377:6yxams50x3/*/POST/visit
Enter fullscreen mode Exit fullscreen mode
  • Enabled CORS for CloudFront + browser requests:

    • Access-Control-Allow-Origin: *
    • Allowed Methods: POST
  • Mistake: Initially got CORS errors and 500 Internal Server Error.

    • Fix: Enabled CORS and corrected Lambda code (ReturnValues + proper JSON body).
  • The Invoke URL:

https://6yxams50x3.execute-api.us-east-1.amazonaws.com/visit
Enter fullscreen mode Exit fullscreen mode
  • Frontend JS calls this URL to increment the counter.

5. CloudFront & S3

  • S3 Bucket: cloud-resume-alok hosts index.html, main.js, style.css, assets/.

  • CloudFront Distribution: d2ruu2h7u4sx55.cloudfront.net points to the S3 bucket.

  • Mistake: Uploading JS with --acl public-read failed (AccessControlListNotSupported).

    • Fix: Removed --acl since the bucket policy handles public access.
  • Mistake: Page showed XML Access Denied.

    • Fix: Enabled proper Origin Access Control (OAC) for CloudFront to read S3 bucket.
  • Invalidated main.js to update browser cache:

aws cloudfront create-invalidation --distribution-id E1UKGUKGZU5H7B --paths /main.js
Enter fullscreen mode Exit fullscreen mode

6. DynamoDB

  • Table: VisitorCounter

  • Partition key: counter (String)

  • Stored visitor count: attribute value (Number).

  • Verified that API calls increment the value:

visits: 7
Enter fullscreen mode Exit fullscreen mode

7. Frontend JS

  • Updated main.js:
document.getElementById("year").textContent = new Date().getFullYear();

const API_URL = "https://6yxams50x3.execute-api.us-east-1.amazonaws.com/visit";

async function incrementVisitorCounter() {
  try {
    const response = await fetch(API_URL, { method: "POST" });
    if (response.ok) {
      const data = await response.json();
      document.getElementById("visitor-count").textContent = data.count;
      console.log("Visitor count updated!");
    } else console.error("Failed to update visitor count", response.statusText);
  } catch (err) {
    console.error("Error calling API", err);
  }
}

incrementVisitorCounter();
Enter fullscreen mode Exit fullscreen mode
  • Added a span in footer to display count:
<p>&copy; <span id="year"></span> Alok | Visitors: <span id="visitor-count"></span></p>
Enter fullscreen mode Exit fullscreen mode
  • Mistake: Earlier, export statements caused Unexpected token 'export' in browser.

    • Fix: Used plain JS for frontend; Lambda uses ES modules internally.

8. Logs & Debugging

  • Used CloudWatch Logs for Lambda errors: /aws/lambda/visitorCounter.

Errors encountered:

  • Runtime.UserCodeSyntaxError → fixed by correcting module syntax.

  • AccessDeniedException → fixed with proper IAM policy.

  • CORS errors → fixed in API Gateway settings.

9. GitHub & Terraform

  • Your frontend repo is on GitHub.

  • S3 + CloudFront can sync automatically using CI/CD pipelines, but we haven’t yet wired Terraform for this.

✅ Status Now

  • Frontend loads from CloudFront.

  • Visitor counter increments on page load.

  • Lambda updates DynamoDB correctly.

  • CORS and permissions fixed.

  • Logs available in CloudWatch.

Final puzzle

  • Terraform automation for the entire stack (S3, CloudFront, Lambda, API Gateway, DynamoDB).

  • Auto-sync from GitHub for frontend updates.

Top comments (0)