task-manager/
├── backend/ # Backend Node.js app
│ ├── package.json
│ └── src/ # Main backend source code
│ ├── handlers/ # Lambda handlers
│ ├── middleware/ # Express middlewares
│ ├── routes/ # Express routes
│ ├── scripts/ # DB setup scripts
│ ├── shared/ # Shared utilities
│ └── server.js # Local express server
├── frontend/ # Frontend React + Vite app
│ ├── public/ # Public static files
│ ├── src/ # Main frontend source code
│ │ ├── api/ # API integrations
│ │ ├── auth/ # AWS Cognito / Amplify auth setup
│ │ ├── components/ # Reusable UI components
│ │ ├── data/ # Mock data for UI testing
│ │ ├── hooks/ # Custom React hooks (e.g. useAuth, useTasks)
│ │ ├── layouts/ # Shared layouts (AppLayout, AuthLayout, Header)
│ │ ├── lib/ # Utility functions & constants
│ │ ├── pages/ # App views (Dashboard, Login, Confirm, etc.)
│ │ ├── App.jsx # Root component
│ │ └── main.jsx # Entry point
│ ├── package.json
│ └── vite.config.js # Vite configuration
├── infra/ # Infrastructure-as-code & AWS setup
│ └── iac/ # SAM/CloudFormation templates
├── docker-compose.yml # Local development orchestration
└── README.md # Project overview and instructionsFollow these steps to set up and run the complete stack (Frontend, Backend, and DynamoDB Local) on your machine.
- Node.js (v18 or higher recommended)
- Docker & Docker Compose (for running DynamoDB locally)
You need to set up environment variables for both the backend and frontend from their respective .env.example templates. Replace the Cognito values with your actual user pool and app client IDs.
Backend:
cd backend
cp .env.example .env# Backend server configuration
PORT=3000
FRONTEND_URL=http://localhost:5173
ALLOWED_ORIGIN=http://localhost:5173
AWS_REGION=ap-southeast-1
DYNAMODB_ENDPOINT=http://localhost:8000
ACCESS_KEY_ID=fake
SECRET_ACCESS_KEY=fake
TABLE_NAME=TasksTable
COGNITO_USER_POOL_ID=your-cognito-user-pool-id
COGNITO_CLIENT_ID=your-cognito-app-client-idFrontend: Open a new terminal or go back to the root, then set up the frontend env file:
cd frontend
cp .env.example .env# Frontend configuration
VITE_API_ENDPOINT=http://localhost:3000
VITE_COGNITO_POOL_ID=your-cognito-user-pool-id
VITE_COGNITO_CLIENT_ID=your-cognito-app-client-id
VITE_AWS_REGION=ap-southeast-1We use Docker to run DynamoDB locally. Make sure the data directory exists and has the correct permissions:
# From the project root
mkdir -p docker/dynamodb
sudo chmod -R 777 docker/dynamodbStart the dynamodb-local and dynamodb-admin containers in the background:
docker compose up -d- DynamoDB Local API:
http://localhost:8000 - DynamoDB Admin UI:
http://localhost:8001(Open in your browser to view tables and data visually)
Here are some helpful commands for managing your local DynamoDB setup:
- View logs:
docker logs dynamodb-local(ordynamodb-admin) - Stop containers:
docker compose down - Stop and wipe database data:
docker compose down -v
Next, install backend dependencies, create the database tables, and start the backend server.
cd backend
npm install
# Initialize table
node src/scripts/createTasksTable.js
# Start the development server
npm run devThe backend server will start listening at http://localhost:3000 (or the port defined in your .env).
In a separate terminal window, navigate to the frontend directory, install dependencies, and start the development server:
cd frontend
npm install
npm run devThe frontend will be available at http://localhost:5173 (or the port defined in your .env).
This project uses Infrastructure as Code (IaC) with AWS SAM and AWS CloudFormation. The main template is located at:
infra/iac/template.yamlThe SAM configuration file is located at:
infra/iac/samconfig.tomlThe IaC stack provisions the core AWS infrastructure for the serverless Task Manager application:
- VPC networking: custom VPC, two private subnets, private route table, Lambda security group
- Private AWS service access: DynamoDB Gateway VPC Endpoint
- Database: DynamoDB table
TasksTablewithuserId-indexGSI - Authentication: Amazon Cognito User Pool and web App Client
- Backend API: API Gateway REST API with Cognito Authorizer and CORS
- Compute: four AWS Lambda functions for task CRUD operations
GET /tasksPOST /tasksPUT /tasks/{id}DELETE /tasks/{id}
- Security and operations: least-privilege IAM roles, CloudWatch Log Groups, API throttling, optional Lambda reserved concurrency
The Lambda source code is packaged from backend/ through the CodeUri values in the SAM template.
Install and configure the following tools before deploying:
- AWS CLI
- AWS SAM CLI
- Node.js v18 or higher
- An AWS account with permissions to create VPC, DynamoDB, Cognito, API Gateway, Lambda, IAM, and CloudWatch resources
Configure AWS credentials:
aws configureOr use a named profile:
aws configure --profile your-profile-nameVerify the active identity:
aws sts get-caller-identityWith a named profile:
aws sts get-caller-identity --profile your-profile-nameThe template restricts Lambda outbound traffic to the AWS-managed DynamoDB prefix list. Get the prefix list ID for your deployment region:
aws ec2 describe-managed-prefix-lists \
--filters "Name=prefix-list-name,Values=com.amazonaws.ap-southeast-1.dynamodb" \
--query "PrefixLists[0].PrefixListId" \
--output text \
--region ap-southeast-1With a named profile:
aws ec2 describe-managed-prefix-lists \
--profile your-profile-name \
--filters "Name=prefix-list-name,Values=com.amazonaws.ap-southeast-1.dynamodb" \
--query "PrefixLists[0].PrefixListId" \
--output text \
--region ap-southeast-1Use the returned value for the DynamoDBPrefixListId parameter in infra/iac/samconfig.toml or in the deploy command.
Open infra/iac/samconfig.toml and review the default deployment values:
stack_name = "task-manager-project2"
region = "ap-southeast-1"
capabilities = "CAPABILITY_IAM CAPABILITY_NAMED_IAM"Important parameters:
ProjectName: prefix for AWS resource namesAllowedOrigin: frontend origin allowed by API Gateway CORSDynamoDBPrefixListId: AWS-managed DynamoDB prefix list ID for the selected regionTableName: DynamoDB table nameEnableReservedConcurrency: keepfalseif the AWS account has limited Lambda concurrency quotaApiRateLimitandApiBurstLimit: API Gateway throttling settings
If the CloudFront or frontend URL changes, update AllowedOrigin and redeploy the stack.
From the project root:
cd infra/iac
sam buildDeploy using the saved configuration in samconfig.toml:
sam deployFor the first deployment or when changing parameters interactively:
sam deploy --guidedIf using a named AWS profile:
sam deploy --profile your-profile-nameSAM will create or update a CloudFormation stack and print the stack outputs after deployment.
After deployment, get the outputs with AWS CLI:
aws cloudformation describe-stacks \
--stack-name task-manager-project2 \
--query "Stacks[0].Outputs" \
--output table \
--region ap-southeast-1With a named profile:
aws cloudformation describe-stacks \
--profile your-profile-name \
--stack-name task-manager-project2 \
--query "Stacks[0].Outputs" \
--output table \
--region ap-southeast-1Important outputs:
ApiUrl: API Gateway endpoint forVITE_API_ENDPOINTUserPoolId: Cognito User Pool ID for frontend configUserPoolClientId: Cognito App Client ID for frontend configDynamoDBTableName: DynamoDB table nameVpcId,PrivateSubnetAId,PrivateSubnetBId,LambdaSecurityGroupId: networking evidence for deployment reports
Update frontend/.env with the deployed values:
VITE_API_ENDPOINT=your-api-url
VITE_COGNITO_POOL_ID=your-user-pool-id
VITE_COGNITO_CLIENT_ID=your-user-pool-client-id
VITE_AWS_REGION=ap-southeast-1Then rebuild the frontend:
cd frontend
npm install
npm run buildDeploy the generated frontend/dist directory to the selected static hosting target, such as S3 + CloudFront:
aws s3 sync dist s3://your-frontend-bucket-name --deleteIf the frontend is served through CloudFront and the browser still receives old files after uploading a new build, create a CloudFront invalidation:
aws cloudfront create-invalidation \
--distribution-id your-cloudfront-distribution-id \
--paths "/*"With a named profile:
aws cloudfront create-invalidation \
--profile your-profile-name \
--distribution-id your-cloudfront-distribution-id \
--paths "/*"After the CloudFront domain changes, update AllowedOrigin in the SAM stack and redeploy the backend stack.
Check CloudFormation stack events:
aws cloudformation describe-stack-events \
--stack-name task-manager-project2 \
--region ap-southeast-1Check failed stack events:
aws cloudformation describe-stack-events \
--stack-name task-manager-project2 \
--query "StackEvents[?contains(ResourceStatus, 'FAILED')].[Timestamp,LogicalResourceId,ResourceType,ResourceStatus,ResourceStatusReason]" \
--output table \
--region ap-southeast-1Delete the stack:
aws cloudformation delete-stack \
--stack-name task-manager-project2 \
--region ap-southeast-1
aws cloudformation wait stack-delete-complete \
--stack-name task-manager-project2 \
--region ap-southeast-1