Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Week 2 of the Cloud Resume Challenge adds a visitor counter to your resume site. When the page loads, its JavaScript calls an HTTP endpoint. API Gateway receives that request and passes it to a Lambda function. The function increments a number stored in a DynamoDB table and returns the new value, which the page displays. The challenge requires the counter and recommends this stack, but it leaves most of the design to you. This guide covers the required parts, the choices you need to make, and a working path through deployment and debugging.

What the challenge requires and what you decide

The challenge’s official AWS page sets out a small set of requirements for this stage:

  • The resume webpage displays a visitor counter, and the value is retrieved from and updated in a backend.
  • The browser must call an API. Browser JavaScript should not connect directly to DynamoDB.
  • The challenge recommends DynamoDB for storage, API Gateway for the HTTP layer, and Lambda for backend logic.
  • Backend resources should be defined as infrastructure as code rather than created by hand in the AWS console. The page recommends AWS SAM and accepts Terraform as an alternative.

The brief does not prescribe the rest. It leaves these to you: whether you use an HTTP API or a REST API, the route name and HTTP method, the partition key and table name, what the counter actually counts, and the CORS policy. The examples in this article are one implementation of those choices, with the reasons given for each. They are not the required solution.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How a single page view travels through the stack

  1. Browser: the page’s JavaScript calls your API URL with fetch() when the page loads.
  2. API Gateway: the request arrives at a route, such as GET /visitors. API Gateway checks it against the CORS configuration and invokes the integration that targets your Lambda function.
  3. Lambda: the function runs your handler code. It updates the counter item in DynamoDB.
  4. DynamoDB: the item’s count is incremented, and the new value is returned to the function.
  5. Response: Lambda returns a JSON body and status code. API Gateway sends it back, and the browser lets the page read it only if CORS allows that origin. The page then writes the count into an element on the page.

Each service has one job. API Gateway handles HTTP concerns, Lambda handles logic, and DynamoDB holds state. Keeping those boundaries clear makes the later debugging much easier.

Decide what the number means before you write code

A visitor counter can measure several different things, and the difference matters both for the code and for what the displayed number honestly claims. The challenge does not define this.

Approach What it measures What it takes to build Main weakness
Request counter Each call to the endpoint One item and one increment per call Refreshes, bots, and prefetching all add to the total
Page-load counter Each time the page’s script runs The same as a request counter, with the page calling the endpoint once per load Still includes repeat visits and automated loads
Unique-visitor counter Distinct visitors over a period An identity method and deduplication, such as hashed IP plus a time window or a cookie, plus storage for seen identities More code, privacy trade-offs, and identity is never perfect

A basic endpoint called on page load counts requests and page loads, not people. If you label the value as unique visitors, you need to implement identity and deduplication to back that claim. For Week 2, a request-based counter is the simpler and more honest choice, and the page can say what it measures.

Design the table and the Lambda handler

The data model

One item is enough. Use a partition key such as id with the value visitors, and store the total in a numeric attribute named count. The key name and table name are your choices. Whatever you pick must match exactly in the function’s code, its environment variables, and the template you deploy.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Increment without losing counts

The simplest approach is to read the item, add one in your code, and write it back. That fails under concurrency. If two requests read 41 at about the same time, both write 42, and one visit disappears. A better approach is to ask DynamoDB to apply the addition to the stored value in the same update call. The UpdateItem operation with an ADD expression does this, and ReturnValues can return the new count without a second read. Check the current DynamoDB developer guide for UpdateItem before you rely on these exact semantics in your build.

The following handler is one way to write it. It uses Python with boto3, which the challenge suggests as a learning path.

import json
import os
import boto3

table = boto3.resource('dynamodb').Table(os.environ['TABLE_NAME'])

def handler(event, context):
    result = table.update_item(
        Key={'id': 'visitors'},
        UpdateExpression='ADD #c :inc',
        ExpressionAttributeNames={'#c': 'count'},
        ExpressionAttributeValues={':inc': 1},
        ReturnValues='UPDATED_NEW',
    )
    count = int(result['Attributes']['count'])
    return {
        'statusCode': 200,
        'headers': {'Content-Type': 'application/json'},
        'body': json.dumps({'count': count}),
    }

Because ADD creates the attribute when it is missing, the first call initializes the counter at 1 without a separate setup step. Keep CORS headers in the API configuration rather than in the handler, so one setting controls them.

One function or several

AWS’s own API Gateway tutorial creates a table, one Lambda function, an HTTP API, a route, and an integration, and it uses a single function for simplicity. AWS also states that separate functions per route are a best practice. For a counter with one route, a single function is a reasonable choice. Split functions once you have several routes that need different permissions or deployment cycles.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose the API type and route

Choice What to compare Notes for this project
HTTP API Setup effort, built-in CORS configuration, and integration with Lambda The AWS tutorial uses this type. Pricing and feature differences should be checked on the current AWS API Gateway pricing and documentation pages, because they change over time.
REST API Richer request and response features, such as request validation and more granular controls Not needed for a single counter route. Setup is more involved.

For the route, GET /visitors is easy to test in a browser. There is one design issue to note: a GET request that increments state is unusual, because GET is normally expected to be safe to repeat. A POST to a route such as /visits describes the action more accurately. Either works for the challenge, but whichever you choose should be consistent in the template, the test commands, and the page’s fetch() call.

Set up CORS between the resume site and the API

The resume page and the API have different origins, so the browser enforces CORS. The API must return an Access-Control-Allow-Origin header that matches the page’s origin, or the browser blocks the response even though the API returned data successfully.

  • Allowed origin: set your site’s exact origin, including the scheme and host, with no path or trailing slash. A mismatch between www and the bare domain, or between http and https, will fail.
  • Allowed methods: include only what the page uses, such as GET. Include OPTIONS handling if your configuration requires it.
  • Wildcard origin: * works for quick local tests, but AWS’s general API Gateway tutorial advises restricting CORS origins in production. Use your real origin for the deployed site.

Curl does not enforce CORS, so a successful curl call does not prove the page can read the response. Test in a browser and check the developer console.

Grant the Lambda role only what it needs

The function’s execution role needs two things. The first is permission to write logs, which the AWS managed policy AWSLambdaBasicExecutionRole provides. The second is permission to update the counter. Grant dynamodb:UpdateItem on that table’s resource, not on all tables, and add dynamodb:GetItem only if your code reads the item directly. Some community examples attach full DynamoDB access to make things work. That is convenient for a demo but is not a least-privilege pattern, so do not copy it into your template.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Deploy with SAM or Terraform

Tool Strengths for this project Trade-offs
AWS SAM The challenge’s recommended tool. Short templates for functions, tables, and HTTP APIs. Commands map directly to deployment steps. AWS-specific. Knowledge transfers less well to multi-cloud work.
Terraform Widely used across clouds and teams. Reusable skills for other infrastructure. More setup and more concepts to learn for a single function. The challenge accepts it as an alternative.

If you choose SAM, a typical workflow looks like this:

  1. Install the AWS CLI and the SAM CLI, then confirm your credentials with aws sts get-caller-identity.
  2. In the template, define a SAM simple table resource, a function resource with the table name passed as an environment variable and the table permission attached, and an HTTP API with its CORS settings.
  3. Run sam build to package the function.
  4. Run sam deploy --guided the first time. Enter the stack name and region, and save the answers so later deploys can run with sam deploy.

Use the same region for the stack, the table, and the function. A region mismatch is one of the most common causes of “not found” errors covered below.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Test the endpoint before you wire up the page

Test the backend on its own first. That separates API problems from browser problems.

  1. Find the invoke URL in the API’s stage details in the API Gateway console, or in the stack outputs your template produces.
  2. Run curl -i against the route. A working endpoint returns HTTP/2 200 and a body such as {"count": 1}.
  3. Run the same command again. The count should rise by exactly one.
  4. Read the item directly with aws dynamodb get-item --table-name YOUR_TABLE_NAME --key '{"id":{"S":"visitors"}}', substituting your table name. The count attribute should match the response.

Once the endpoint works, add the fetch() call to the page. Write the returned count into an element, and show a fallback message if the request fails so the page never displays a broken value.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Troubleshoot the common failures

Symptom Likely cause What to check
Browser console says the request was blocked by CORS policy, but curl works The allowed origin does not match the page’s exact origin, or the API was not redeployed after a CORS change Compare the page’s origin with the configured origin, including scheme and www. Redeploy and hard-refresh.
HTTP 500 from the endpoint The Lambda function threw an error Open the function’s log group in CloudWatch Logs and read the most recent error.
AccessDeniedException in the logs The execution role lacks the update permission, or the policy names a different table Check the role’s policy against the table’s ARN and the action dynamodb:UpdateItem.
ResourceNotFoundException in the logs The TABLE_NAME environment variable is wrong, or the table is in another region Compare the environment variable with the deployed table name and confirm both are in the same region.
Count resets or never increases The function writes to a different key than the one you read, or a second table is in use Check the partition key value in the handler and in your get-item test.
Page shows “undefined” or a blank count The JSON field name in the response does not match what the page reads Log the parsed response in the browser console and match the field name exactly.

When an endpoint fails, the page should show a fallback message rather than an empty number. That keeps a backend outage from looking like a real count.

Limits of this design and where to learn more

  • The number is a request count. Refreshes, bots, and automated checks all increase it. Describe it that way on the page, or add identity handling if you want a unique-visitor figure.
  • Cost depends on your account. AWS’s API Gateway tutorial states that its exercise can be completed within the AWS Free Tier. Actual charges depend on account eligibility, region, and usage, so check your billing dashboard after deploying.
  • Console labels change. The steps above name the services and actions, but menu labels in the AWS console change over time. Check the current AWS documentation when a screen differs.

For further reading, the challenge’s AWS page is the authoritative description of the requirements. AWS’s API Gateway tutorial shows the HTTP API and Lambda and DynamoDB flow in general terms, and the AWS Lambda getting-started guide covers the function model. The AWS edition of The Cloud Resume Challenge book is listed on the challenge page as optional further learning. Check its current listing and edition before buying.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.