A modern, serverless three-tier-architecture web application built on AWS, demonstrating best practices for cloud-native architecture using React, AWS Lambda, API Gateway, DynamoDB, S3, and CloudFront.
- Architecture Overview
- Project Structure
- Prerequisites
- Getting Started
- Architecture Deep Dive
- IAM Roles & Permissions
- Service Interactions
- CORS Configuration
- Deployment
- Cleanup
- API Documentation
- Troubleshooting
- Contributing
- Future Enhancements
This application implements a three-tier serverless architecture on AWS:
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β Presentation β β Application β β Data β
β Tier β β Tier β β Tier β
βββββββββββββββββββ€ ββββββββββββββββββββ€ βββββββββββββββββββ€
β React SPA β β AWS Lambda β β DynamoDB β
β S3 Bucket βββββΊβ API Gateway βββββΊβ NoSQL DB β
β CloudFront β β Python 3.13 β β Pay-per-use β
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
- β Serverless: No server management required
- β Scalable: Auto-scaling based on demand
- β Cost-effective: Pay only for what you use
- β Secure: IAM-based access control
- β Fast: Global CDN distribution
- β Modern: React SPA with responsive design
aws-three-tier-architecture/
βββ frontend/ # Presentation Tier (React SPA)
β βββ src/
β β βββ components/ # React components
β β β βββ TodoForm.jsx # Todo creation/editing form
β β β βββ TodoItem.jsx # Individual todo item
β β β βββ TodoList.jsx # Todo list container
β β βββ App.jsx # Main application component
β β βββ main.jsx # Application entry point
β βββ package.json # Dependencies and scripts
β βββ vite.config.js # Vite build configuration
βββ sam-app/ # Application & Data Tiers (SAM)
β βββ todo_function/ # Lambda function code
β β βββ app.py # Main Lambda handler
β β βββ requirements.txt # Python dependencies
β βββ events/ # Test events for local development
β βββ tests/ # Unit and integration tests
β βββ template.yaml # SAM/CloudFormation template
βββ deploy.sh # Automated deployment script
βββ delete.sh # Cleanup script
-
AWS CLI - Configure AWS credentials
aws configure
-
AWS SAM CLI - Deploy serverless applications
# macOS brew install aws-sam-cliFollow: SAM CLI Installation Guide
-
Node.js & npm - Frontend development
node --version # v18+ recommended npm --version -
Python 3.13 - Lambda runtime
python3 --version
- Active AWS account with appropriate permissions
- IAM user with programmatic access
- Sufficient service limits for:
- Lambda functions
- API Gateway APIs
- DynamoDB tables
- S3 buckets
- CloudFront distributions
-
Clone the repository
git clone <repository-url> cd aws-three-tier-architecture
-
Make deployment script executable
chmod +x deploy.sh
-
Deploy the application
./deploy.sh
-
Access your application
- The script will output the CloudFront URL
- Open the URL in your browser
If you prefer manual deployment:
-
Deploy SAM application
cd sam-app sam build sam deploy --guided -
Build and deploy frontend
cd ../frontend npm install npm run build # Upload to S3 (replace with your bucket name) aws s3 sync dist/ s3://your-bucket-name --delete
- Technology: React 18 with Vite
- Hosting: Amazon S3 static website hosting
- CDN: Amazon CloudFront for global distribution
- Features:
- Single Page Application (SPA)
- Responsive design
- Environment-based API configuration
- Compute: AWS Lambda (Python 3.13)
- API: Amazon API Gateway (REST API)
- Features:
- Serverless compute
- Auto-scaling
- CORS enabled
- RESTful API design
- Database: Amazon DynamoDB
- Configuration:
- Pay-per-request billing
- Single table design
- Partition key:
id(String)
The SAM template automatically creates an IAM role for the Lambda function with these policies:
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref DynamoDBTablePurpose: Grants Lambda function permissions to perform CRUD operations on the DynamoDB table.
AWS Managed Policy: Provides these permissions:
dynamodb:GetItem- Read individual itemsdynamodb:PutItem- Create new itemsdynamodb:UpdateItem- Modify existing itemsdynamodb:DeleteItem- Remove itemsdynamodb:Scan- Read all itemsdynamodb:Query- Query items with conditions
Documentation: DynamoDB IAM Policies
Policies:
- AWSLambdaBasicExecutionRolePurpose: Provides basic Lambda execution permissions.
AWS Managed Policy: Includes:
logs:CreateLogGroup- Create CloudWatch log groupslogs:CreateLogStream- Create log streamslogs:PutLogEvents- Write logs to CloudWatch
Documentation: Lambda Execution Role
CloudFrontS3AccessBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
PolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: cloudfront.amazonaws.com
Action: s3:GetObject
Resource: !Sub "arn:aws:s3:::${FrontendBucket}/*"
Condition:
StringEquals:
AWS:SourceArn: !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}"Purpose: Allows CloudFront to access S3 bucket objects while keeping the bucket private.
Security Benefits:
- S3 bucket remains private (no public access)
- Only CloudFront can access bucket contents
- Prevents direct S3 access bypassing CloudFront
Documentation: CloudFront OAC
User Browser β CloudFront Distribution
- User accesses application via CloudFront URL
- CloudFront serves cached content when possible
- Routes requests based on path patterns
CloudFront β S3 Bucket (via OAC)
Configuration in template.yaml:
Origins:
- Id: S3Origin
DomainName: !GetAtt FrontendBucket.RegionalDomainName
OriginAccessControlId: !GetAtt OriginAccessControl.Id- React SPA files served from S3
- OAC ensures secure access
- Cached globally for performance
CloudFront β API Gateway β Lambda β DynamoDB
CloudFront Cache Behavior:
CacheBehaviors:
- PathPattern: "/api/*"
TargetOriginId: ApiOrigin
MinTTL: 0
DefaultTTL: 0
MaxTTL: 0 # No caching for API callsAPI Gateway Integration:
Origins:
- Id: ApiOrigin
DomainName: !Sub "${TodoAPI}.execute-api.${AWS::Region}.amazonaws.com"
OriginPath: "/prod"Event Configuration in template.yaml:
Events:
GetTodos:
Type: Api
Properties:
RestApiId: !Ref TodoAPI
Path: /api/todos
Method: getLambda Handler Logic:
def lambda_handler(event, context):
method = event.get('httpMethod')
path_parameters = event.get('pathParameters') or {}
todo_id = path_parameters.get('id')
if method == 'GET' and not todo_id:
return get_todos()
elif method == 'POST' and not todo_id:
return create_todo(body)
# ... other routesTable Configuration:
DynamoDBTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: todos
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
BillingMode: PAY_PER_REQUESTLambda DynamoDB Access:
# Initialize DynamoDB resource
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("todos")
# CRUD operations
def get_todos():
result = table.scan()
return response(200, result["Items"])
def create_todo(data):
todo = {
"id": str(uuid.uuid4()),
"text": data["text"],
"completed": False,
"createdAt": now_iso_ms(),
}
table.put_item(Item=todo)
return response(201, todo)SAM Template Global Variables:
Globals:
Function:
Environment:
Variables:
BUCKET_NAME: !Ref BucketName
TABLE_NAME: !Ref DynamoDBTableReact Environment Configuration:
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || "/api";Cross-Origin Resource Sharing (CORS) is critical for this architecture since the React frontend (served from CloudFront) needs to make API calls to API Gateway. This application implements CORS at multiple levels:
Template Configuration:
TodoAPI:
Type: AWS::Serverless::Api
Properties:
StageName: prod
Cors:
AllowMethods: "'GET,POST,PUT,DELETE,OPTIONS'"
AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
AllowOrigin: "'*'"Purpose: Enables API Gateway to handle preflight OPTIONS requests and set appropriate CORS headers.
Key Settings:
AllowMethods: Permits all HTTP methods used by the applicationAllowHeaders: Allows standard headers plus AWS-specific headersAllowOrigin: Set to'*'for development (restrict in production)
Documentation: API Gateway CORS
Response Headers in app.py:
def response(status_code, body):
return {
"statusCode": status_code,
"headers": {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type"
},
"body": json.dumps(body, default=str)
}Purpose: Ensures every Lambda response includes CORS headers for browser compatibility.
CloudFront Configuration:
CacheBehaviors:
- PathPattern: "/api/*"
ForwardedValues:
Headers:
- Authorization
- Content-TypePurpose: Ensures CloudFront forwards necessary headers to API Gateway for CORS processing.
Solution: Verify both API Gateway and Lambda function return CORS headers
Solution: Add Content-Type to AllowHeaders in API Gateway configuration
Solution: Ensure API Gateway handles OPTIONS method correctly and returns appropriate CORS headers
SAM Template Global Variables:
Globals:
Function:
Environment:
Variables:
BUCKET_NAME: !Ref BucketName
TABLE_NAME: !Ref DynamoDBTableReact Environment Configuration:
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || "/api";The deploy.sh script automates the entire deployment process:
Key Features:
- Builds and deploys SAM application
- Extracts CloudFormation outputs
- Configures React environment variables
- Uploads built frontend to S3
- Provides final application URL
-
Deploy Backend Infrastructure
cd sam-app sam build sam deploy --guided -
Configure Frontend Environment
cd ../frontend echo "VITE_API_BASE_URL=https://your-cloudfront-url/api" > .env.production
-
Build and Deploy Frontend
npm install npm run build aws s3 sync dist/ s3://your-bucket-name --delete
The delete.sh script safely removes all AWS resources:
Key Features:
- Deletes CloudFormation stack
- Removes S3 bucket and its contents
- Ensures no orphaned resources remain
Important: S3 buckets must be empty before CloudFormation can delete them.
-
Empty S3 Bucket
aws s3 rm s3://your-bucket-name --recursive
-
Delete SAM Stack
cd sam-app sam delete
https://your-cloudfront-url/api
Retrieve all todos
curl https://your-cloudfront-url/api/todosResponse:
[
{
"id": "uuid",
"text": "Sample todo",
"completed": false,
"createdAt": "2024-01-01T00:00:00.000Z"
}
]Create a new todo
curl -X POST https://your-cloudfront-url/api/todos \
-H "Content-Type: application/json" \
-d '{"text": "New todo item"}'Update an existing todo
curl -X PUT https://your-cloudfront-url/api/todos/uuid \
-H "Content-Type: application/json" \
-d '{"text": "Updated text", "completed": true}'Delete a todo
curl -X DELETE https://your-cloudfront-url/api/todos/uuidSymptom: Browser console shows CORS errors Solution: Verify API Gateway CORS configuration in template.yaml
Symptom: CloudFront returns 403 for static files Solution: Check S3 bucket policy and OAC configuration
Symptom: 500 errors from API Solution: Check CloudWatch logs for Lambda function
Symptom: Lambda cannot access DynamoDB Solution: Verify IAM role has DynamoDBCrudPolicy
# Check SAM deployment status
sam list stack-outputs
# View Lambda logs
sam logs -n TodoFunction --stack-name your-stack-name
# Test Lambda function locally
sam local start-api
# Validate SAM template
sam validate- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Built using AWS Serverless Technologies
This section outlines potential improvements and enhancements that could be implemented to further strengthen the application's security, scalability, reliability, and performance.
- AWS Cognito Integration: Implement user authentication and authorization
- JWT Token Validation: Add token-based authentication for API endpoints
- API Key Management: Implement API keys for rate limiting and access control
- Role-Based Access Control (RBAC): Define granular permissions for different user roles
- Least Privilege Principle: Further restrict IAM policies to minimum required permissions
- Cross-Account Roles: Implement cross-account access for multi-environment deployments
- Resource-Based Policies: Add more granular resource-level permissions
- IAM Policy Conditions: Implement time-based and IP-based access restrictions
- Domain-Specific CORS: Replace wildcard (
*) with specific domain origins in production - Credential-Aware CORS: Implement
Access-Control-Allow-Credentialsfor authenticated requests - Header Validation: Restrict allowed headers to only necessary ones
- Method Restrictions: Limit HTTP methods based on endpoint requirements
- DynamoDB Encryption: Enable encryption at rest and in transit
- S3 Bucket Encryption: Implement server-side encryption for static assets
- Secrets Management: Use AWS Secrets Manager for sensitive configuration
- Input Validation: Add comprehensive input sanitization and validation
- DynamoDB DAX: Implement DynamoDB Accelerator for microsecond latency
- API Gateway Caching: Enable response caching for read-heavy operations
- CloudFront Edge Caching: Optimize cache behaviors and TTL settings
- Lambda Provisioned Concurrency: Reduce cold start latency for critical functions
- DynamoDB Global Secondary Indexes (GSI): Add indexes for efficient querying
- Batch Operations: Implement batch read/write operations for bulk data
- Connection Pooling: Optimize database connections in Lambda functions
- Query Optimization: Replace scan operations with more efficient query patterns
- DynamoDB Auto Scaling: Configure automatic capacity scaling based on demand
- Lambda Concurrency Limits: Set appropriate reserved and provisioned concurrency
- API Gateway Throttling: Implement rate limiting and burst capacity management
- CloudFront Geographic Restrictions: Optimize content delivery based on user location
- Cross-Region Replication: Implement DynamoDB Global Tables for disaster recovery
- Multi-Region CloudFront: Deploy edge locations closer to global users
- Route 53 Health Checks: Add DNS failover and health monitoring
- Regional Lambda Deployments: Deploy functions in multiple regions for redundancy
- Circuit Breaker Pattern: Implement failure isolation and recovery mechanisms
- Retry Logic: Add exponential backoff for transient failures
- Dead Letter Queues: Implement DLQ for failed Lambda invocations
- Graceful Degradation: Design fallback mechanisms for service failures
- AWS X-Ray Tracing: Implement distributed tracing for request flow analysis
- Custom CloudWatch Metrics: Add business-specific metrics and dashboards
- Log Aggregation: Centralize logs using CloudWatch Logs Insights
- Alerting Strategy: Set up proactive alerts for system health and performance
- Automated Backups: Implement point-in-time recovery for DynamoDB
- Cross-Region Backup: Store backups in multiple regions
- Infrastructure as Code Versioning: Version control for SAM templates
- Recovery Testing: Regular disaster recovery drills and testing
- Multi-Stage Deployments: Implement dev/staging/prod pipeline
- Automated Testing: Add unit, integration, and end-to-end tests
- Security Scanning: Integrate SAST/DAST tools in deployment pipeline
- Blue-Green Deployments: Implement zero-downtime deployment strategies
- Environment Separation: Separate AWS accounts for different environments
- Resource Tagging Strategy: Implement comprehensive tagging for cost allocation
- Cost Optimization: Regular cost analysis and resource right-sizing
- Compliance Monitoring: Implement AWS Config for compliance tracking
- API Versioning: Implement versioning strategy for backward compatibility
- Request/Response Validation: Add JSON schema validation
- Rate Limiting: Implement per-user and per-endpoint rate limiting
- API Documentation: Auto-generate OpenAPI/Swagger documentation
- Domain-specific CORS configuration
- Input validation and sanitization
- CloudWatch monitoring and alerting
- Automated backup strategy
- AWS Cognito authentication
- DynamoDB performance optimization
- Multi-environment CI/CD pipeline
- Enhanced error handling
- Multi-region deployment
- Advanced analytics implementation
- Real-time features
- Mobile application development
These enhancements should be prioritized based on business requirements, user feedback, and system performance metrics. Each improvement should be implemented incrementally with proper testing and monitoring.