โ† Back to projects

Serverless Visitor Counter with Lambda, API Gateway and DynamoDB

The visitor count on my portfolio is not hardcoded. It is a real-time serverless counter built on AWS. Every visit triggers a Lambda function, increments a DynamoDB record, and returns the live count.

AWS Lambda API Gateway DynamoDB Python 3.12 IAM CORS
Architecture Visualization
๐ŸŒ
Browser Visit Portfolio page load
โ†’
๐ŸŒ
API Gateway GET /count route
โ†’
โšก
AWS Lambda Python function
โ†’
๐Ÿ—„๏ธ
DynamoDB Atomic counter
โ†’
๐Ÿ”ข
Live Count JSON response

Every visit triggers this serverless pipeline and returns the latest count without running a traditional backend server.

The Strategic Why

Why serverless for a visitor counter?

A visitor counter is a small feature, but it demonstrates a complete serverless architecture where every component is managed by AWS with no server maintenance.

With a traditional backend, I would need a server running continuously, a database to manage, and infrastructure to patch and monitor. With serverless, the Lambda function only runs when someone visits. No traffic means no compute cost. No servers means less operational overhead.

For a portfolio site with irregular traffic, serverless is a practical fit: available on demand, simple to scale, and extremely low cost at this size.
Execution Timeline

Step by step breakdown

Here is the path from a static portfolio page to a working AWS-powered visitor counter.

01

Created the DynamoDB table

Created a table named visitor-counter with a partition key of id. DynamoDB works well here because the project only needs one record that stores the running count.

02

Created the Lambda function in Python

Created a Lambda function named visitor-counter with the Python 3.12 runtime. The function uses boto3 to update DynamoDB whenever the endpoint is called.

03

Attached DynamoDB permissions to Lambda

Lambda functions run under an IAM role. I attached DynamoDB access to the execution role so the function could read and write the visitor counter table.

04

Created an HTTP API in API Gateway

Created an HTTP API named visitor-counter-api with a GET /count route. API Gateway receives the browser request, invokes Lambda, and returns the response.

05

Connected API Gateway to Lambda

Attached a Lambda integration to the GET /count route so API Gateway knows which function should handle the request.

06

Updated the portfolio JavaScript

Replaced the placeholder counter URL in index.js with the API Gateway invoke URL. The portfolio now calls the API on page load and renders the live count.

The Code

Lambda function code

The backend logic is small, but it covers the core ideas: AWS SDK access, atomic DynamoDB updates, JSON output and CORS headers.

lambda_function.py - Python 3.12
import json import boto3 # Connect to DynamoDB. boto3 uses the Lambda execution role credentials. dynamodb = boto3.resource('dynamodb') table = dynamodb.Table('visitor-counter') def lambda_handler(event, context): # Atomic increment: thread-safe and protected from race conditions. response = table.update_item( Key={'id': 'visitors'}, UpdateExpression='ADD #count :increment', ExpressionAttributeNames={'#count': 'count'}, ExpressionAttributeValues={':increment': 1}, ReturnValues='UPDATED_NEW' ) count = int(response['Attributes']['count']) return { 'statusCode': 200, 'headers': { # CORS: only allow requests from the portfolio domain. 'Access-Control-Allow-Origin': 'https://pavankrishna.dev', 'Access-Control-Allow-Methods': 'GET', 'Content-Type': 'application/json' }, 'body': json.dumps({'count': count}) }

Why use ADD instead of GET then SET? DynamoDB's ADD operation increments the value in one atomic operation. If two visitors arrive at the same time, both increments are counted correctly.

Why use ExpressionAttributeNames? The word count is reserved in DynamoDB. Using #count as an alias avoids a syntax error.

Obstacles & Solutions

Challenges and how I fixed them

Problem

IAM user lacked permissions

When trying to attach a policy to the Lambda execution role, the console returned an access denied error. The deployment IAM user did not have permission to manage IAM policies.

Solution

Added the missing IAM permissions, then returned to attach DynamoDB access to the Lambda role. The key lesson was that every AWS service connection needs explicit permission.

Problem

Counter showed N/A during local testing

After updating the counter URL and testing locally, the page displayed N/A instead of the real count. The browser request was blocked because local development was not an allowed origin.

Solution

Kept CORS restricted to https://pavankrishna.dev and tested the counter on the deployed site after pushing to GitHub. This prevents other websites from calling the API and inflating the count.

Problem

API returned 404 Not Found on first test

The API Gateway invoke URL returned {"message": "Not Found"} because the GET /count route was not yet attached to the Lambda integration.

Solution

Attached the Lambda integration to the route, waited briefly for the change to apply, and retested the full /count URL. The endpoint then returned the visitor count correctly.

Financial Breakdown

What does this actually cost?

Serverless pricing is based on usage. For a personal portfolio, this project is effectively free.

Service Free tier allowance Monthly cost
AWS Lambda 1 million requests per month plus 400,000 GB-seconds compute ~โ‚ฌ0
API Gateway HTTP API 1 million API calls per month for the first 12 months ~โ‚ฌ0
DynamoDB 25 GB storage plus 25 read and write capacity units โ‚ฌ0
IAM Always free โ‚ฌ0
Total Real-time serverless counter, available on demand ~โ‚ฌ0

At typical portfolio traffic levels, the cost remains near zero even beyond the free tier.

Key Technical Takeaways

What I learned from this build

โšก

Serverless removes server maintenance

Lambda scales from zero and only runs when called. The mental model changes from managing servers to composing managed services.

๐Ÿ”’

CORS is a security feature

Restricting API access to the portfolio domain prevents other websites from calling the endpoint and manipulating the count.

โš›๏ธ

Atomic operations matter

DynamoDB's atomic update prevents race conditions. Even small projects should be correct under concurrent requests.

๐Ÿ”—

IAM connects AWS services

Lambda cannot access DynamoDB without explicit IAM permission. Permissions are the glue between AWS services.

๐Ÿงช

Test each layer separately

Testing Lambda first, then API Gateway, made it easier to locate errors before connecting the full flow.

๐Ÿ’ฐ

Low-scale cloud can be cheap

For a portfolio visitor counter, Lambda and DynamoDB stay comfortably within free-tier style usage.