A production-ready, serverless web app that serves a frontend site from S3/CloudFront and uses an AWS Lambda function with DynamoDB to track page views.
- Frontend hosted on
Amazon S3(optionally viaCloudFront). - Serverless backend using
AWS Lambdawith aFunction URL(or API Gateway). DynamoDBtable stores and increments a single counter for page views.- Zero servers to manage, fast global delivery with CloudFront, and pay-per-use compute.
- Front end:
HTML,CSS,JavaScript(vanilla) - Backend:
AWS Lambda(Python) - Data:
Amazon DynamoDB - Hosting/CDN:
Amazon S3,Amazon CloudFront
index.html # Static page UI (form + counter)
style.css # Styles
script.js # Front-end logic and backend call
lambda-function.py# Lambda handler that increments the counter in DynamoDB
README.md # This file
- The browser loads the static site from S3 (optionally via CloudFront).
script.jscalls the Lambda Function URL to fetch/increment the view count.- The Lambda function reads item
id = "0"from DynamoDB, incrementsviews, writes it back, and returns the count as JSON.
Prerequisites:
- AWS account with permissions for S3, CloudFront (optional), Lambda, and DynamoDB
- AWS CLI configured (
aws configure) - Python 3.9+ for packaging the Lambda (if needed)
- Table name:
serverless-web-application-on-aws - Partition key:
id(String) - Create a seed item with
id = "0"andviews = 0.
Using AWS CLI (replace region as needed):
aws dynamodb create-table --table-name serverless-web-application-on-aws --attribute-definitions AttributeName=id,AttributeType=S --key-schema AttributeName=id,KeyType=HASH --billing-mode PAY_PER_REQUEST --region us-east-1
aws dynamodb put-item --table-name serverless-web-application-on-aws --item {"id":{"S":"0"},"views":{"N":"0"}} --region us-east-1This repo’s lambda-function.py uses the table name serverless-web-application-on-aws. You can keep that name or change the code to read a TABLE_NAME env var.
Steps (console) — simplest path:
- Create a Lambda function (Python 3.10 or similar).
- Add basic execution role with permissions to read/write the DynamoDB table.
- Create a Function URL (Auth = NONE) and enable CORS (e.g., allow your S3/CloudFront origin).
- Upload the code from
lambda-function.py.
Or package with CLI:
copy lambda-function.py function\lambda-function.py
cd function
tar -a -c -f function.zip lambda-function.py
cd ..
rem Create function (first time)
aws lambda create-function --function-name view-counter --runtime python3.10 --role arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_WITH_DDB_ACCESS> --handler lambda-function.lambda_handler --zip-file fileb://function/function.zip --region us-east-1
rem Or update code (subsequent deployments)
aws lambda update-function-code --function-name view-counter --zip-file fileb://function/function.zip --region us-east-1
rem Create Function URL (one time)
aws lambda create-function-url-config --function-name view-counter --auth-type NONE --cors AllowOrigins='["*"]',AllowMethods='["GET"]' --region us-east-1
rem Get Function URL
aws lambda get-function-url-config --function-name view-counter --region us-east-1Copy the Function URL you get and update the front end (next step).
Edit script.js and set your Function URL:
const counter = document.querySelector(".counter-number");
async function updateCounter() {
let response = await fetch("https://YOUR_FUNCTION_ID.lambda-url.YOUR-REGION.on.aws/");
let data = await response.json();
counter.innerHTML = `Views: ${data}`;
}
updateCounter();aws s3 mb s3://<your-unique-bucket-name> --region us-east-1
aws s3 website s3://<your-unique-bucket-name>/ --index-document index.html --error-document index.html
aws s3 cp index.html s3://<your-unique-bucket-name>/ --acl public-read
aws s3 cp style.css s3://<your-unique-bucket-name>/ --acl public-read
aws s3 cp script.js s3://<your-unique-bucket-name>/ --acl public-readMake sure the bucket policy or S3 Public Access settings allow static website hosting (or use CloudFront, recommended for production).
- Create a CloudFront distribution with the S3 website endpoint as origin.
- Set a default behavior to allow GET/HEAD.
- Add your custom domain + SSL via ACM for a polished public URL.
You can open index.html directly or use a local server for CORS-friendly testing:
python -m http.server 8000Note: The counter still calls the deployed Lambda URL.
- IAM least privilege: Lambda role should allow only the specific DynamoDB table access.
- CORS: Restrict Function URL CORS to your S3/CloudFront origin in production.
- API protection: Consider placing Lambda behind API Gateway with a WAF and authentication for public endpoints.
- Costs: Uses pay-per-request services; typical demo usage is cents/month, but always monitor with AWS Budgets.
I designed this as a minimal, production-minded serverless pattern: static hosting + a small compute function + managed NoSQL. It’s fast to deploy, easy to scale, and showcases cloud-native fundamentals.
MIT
