<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Isaac Obuor</title>
    <description>The latest articles on DEV Community by Isaac Obuor (@isaac_obuor_4ec2278316110).</description>
    <link>https://dev.to/isaac_obuor_4ec2278316110</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1732054%2F6ff13cfe-debf-4fb6-9171-4b3e5ee0ce0b.png</url>
      <title>DEV Community: Isaac Obuor</title>
      <link>https://dev.to/isaac_obuor_4ec2278316110</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/isaac_obuor_4ec2278316110"/>
    <language>en</language>
    <item>
      <title>Secure E-commerce Platform on AWS-Infrastructure</title>
      <dc:creator>Isaac Obuor</dc:creator>
      <pubDate>Tue, 01 Jul 2025 21:49:53 +0000</pubDate>
      <link>https://dev.to/isaac_obuor_4ec2278316110/secure-e-commerce-platform-on-aws-infrastructure-4791</link>
      <guid>https://dev.to/isaac_obuor_4ec2278316110/secure-e-commerce-platform-on-aws-infrastructure-4791</guid>
      <description>&lt;p&gt;This comprehensive project will give you hands-on experience with all the AWS services mentioned in the job posting. Here's why this project is perfect for demonstrating your qualifications:&lt;/p&gt;

&lt;p&gt;Key Benefits:&lt;/p&gt;

&lt;p&gt;. Real-world scenario: E-commerce platform mirrors actual business requirements&lt;br&gt;
. Production-ready: Includes security, monitoring, and compliance features&lt;br&gt;
. Scalable architecture: Demonstrates understanding of enterprise-level design&lt;br&gt;
. Full CI/CD pipeline: Shows DevOps integration skills&lt;br&gt;
. Security-first approach: Meets compliance requirements that employers value&lt;/p&gt;

&lt;p&gt;What Makes This Stand Out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Multi-tier architecture showing deep VPC understanding&lt;/li&gt;
&lt;li&gt;Container orchestration with ECS Fargate (modern approach)&lt;/li&gt;
&lt;li&gt;Blue/green deployments demonstrating zero-downtime strategies&lt;/li&gt;
&lt;li&gt;Comprehensive security including WAF, encryption, and monitoring&lt;/li&gt;
&lt;li&gt;Cost optimization through proper resource sizing and lifecycle policies&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AWS Portfolio Project - Step-by-Step Implementation Guide&lt;br&gt;
Pre-requisites Setup (Day 0)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AWS Account Setup
bash# Create AWS Account (if you don't have one)
# Sign up at: &lt;a href="https://aws.amazon.com/" rel="noopener noreferrer"&gt;https://aws.amazon.com/&lt;/a&gt;
# Enable billing alerts in CloudWatch
# Set up MFA for root account&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;
  
  
  Install AWS CLI
&lt;/h1&gt;

&lt;p&gt;curl "&lt;a href="https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" rel="noopener noreferrer"&gt;https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip&lt;/a&gt;" -o "awscliv2.zip"&lt;br&gt;
unzip awscliv2.zip&lt;br&gt;
sudo ./aws/install&lt;/p&gt;
&lt;h1&gt;
  
  
  Configure AWS CLI
&lt;/h1&gt;

&lt;p&gt;aws configure&lt;/p&gt;
&lt;h1&gt;
  
  
  Enter your Access Key ID, Secret Access Key, Region (us-east-1), and output format (json)
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;Development Environment
bash# Install required tools
# Node.js (for sample application)
curl -fsSL &lt;a href="https://deb.nodesource.com/setup_18.x" rel="noopener noreferrer"&gt;https://deb.nodesource.com/setup_18.x&lt;/a&gt; | sudo -E bash -
sudo apt-get install -y nodejs&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;
  
  
  Docker (for containerization)
&lt;/h1&gt;

&lt;p&gt;sudo apt-get update&lt;br&gt;
sudo apt-get install docker.io&lt;br&gt;
sudo usermod -aG docker $USER&lt;/p&gt;
&lt;h1&gt;
  
  
  Git (for version control)
&lt;/h1&gt;

&lt;p&gt;sudo apt-get install git&lt;/p&gt;
&lt;h1&gt;
  
  
  VS Code or your preferred editor
&lt;/h1&gt;

&lt;p&gt;wget -qO- &lt;a href="https://packages.microsoft.com/keys/microsoft.asc" rel="noopener noreferrer"&gt;https://packages.microsoft.com/keys/microsoft.asc&lt;/a&gt; | gpg --dearmor &amp;gt; packages.microsoft.gpg&lt;br&gt;
sudo install -o root -g root -m 644 packages.microsoft.gpg /etc/apt/trusted.gpg.d/&lt;br&gt;
sudo sh -c 'echo "deb [arch=amd64,arm64,armhf signed-by=/etc/apt/trusted.gpg.d/packages.microsoft.gpg] &lt;a href="https://packages.microsoft.com/repos/code" rel="noopener noreferrer"&gt;https://packages.microsoft.com/repos/code&lt;/a&gt; stable main" &amp;gt; /etc/apt/sources.list.d/vscode.list'&lt;br&gt;
sudo apt update&lt;br&gt;
sudo apt install code&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Project Structure Setup
bash# Create project directory
mkdir aws-ecommerce-infrastructure
cd aws-ecommerce-infrastructure&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;
  
  
  Create directory structure
&lt;/h1&gt;

&lt;p&gt;mkdir -p {&lt;br&gt;
  cloudformation/{network,security,compute,storage,cicd},&lt;br&gt;
  application/{frontend,backend},&lt;br&gt;
  scripts,&lt;br&gt;
  documentation,&lt;br&gt;
  monitoring&lt;br&gt;
}&lt;/p&gt;
&lt;h1&gt;
  
  
  Initialize Git repository
&lt;/h1&gt;

&lt;p&gt;git init&lt;br&gt;
echo "# AWS E-commerce Infrastructure Project" &amp;gt; README.md&lt;br&gt;
git add README.md&lt;br&gt;
git commit -m "Initial commit"&lt;/p&gt;

&lt;p&gt;WEEK 1: Network Foundation &amp;amp; Security&lt;br&gt;
Day 1: VPC and Networking Setup&lt;br&gt;
Step 1: Create Base VPC CloudFormation Template&lt;br&gt;
bash# Create the main network template&lt;br&gt;
touch cloudformation/network/vpc-base.yaml&lt;br&gt;
File: cloudformation/network/vpc-base.yaml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWSTemplateFormatVersion: '2010-09-09'
Description: 'E-commerce VPC with public and private subnets'

Parameters:
  ProjectName:
    Type: String
    Default: ecommerce
    Description: Name of the project

  Environment:
    Type: String
    Default: production
    AllowedValues: [development, staging, production]

Resources:
  # VPC
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-vpc'
        - Key: Environment
          Value: !Ref Environment

  # Internet Gateway
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-igw'

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

  # Public Subnets
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs '']
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-public-subnet-1'

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [1, !GetAZs '']
      CidrBlock: 10.0.2.0/24
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-public-subnet-2'

  # Private Subnets
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs '']
      CidrBlock: 10.0.10.0/24
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-private-subnet-1'

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [1, !GetAZs '']
      CidrBlock: 10.0.20.0/24
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-private-subnet-2'

  # Database Subnets
  DBSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs '']
      CidrBlock: 10.0.30.0/24
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-db-subnet-1'

  DBSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [1, !GetAZs '']
      CidrBlock: 10.0.40.0/24
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-db-subnet-2'

  # NAT Gateways
  NatGateway1EIP:
    Type: AWS::EC2::EIP
    DependsOn: InternetGatewayAttachment
    Properties:
      Domain: vpc

  NatGateway2EIP:
    Type: AWS::EC2::EIP
    DependsOn: InternetGatewayAttachment
    Properties:
      Domain: vpc

  NatGateway1:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGateway1EIP.AllocationId
      SubnetId: !Ref PublicSubnet1

  NatGateway2:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGateway2EIP.AllocationId
      SubnetId: !Ref PublicSubnet2

  # Route Tables
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-public-routes'

  DefaultPublicRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1

  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet2

  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-private-routes-1'

  DefaultPrivateRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway1

  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      SubnetId: !Ref PrivateSubnet1

  PrivateRouteTable2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-private-routes-2'

  DefaultPrivateRoute2:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway2

  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      SubnetId: !Ref PrivateSubnet2

Outputs:
  VPC:
    Description: A reference to the created VPC
    Value: !Ref VPC
    Export:
      Name: !Sub '${ProjectName}-${Environment}-VPC'

  PublicSubnets:
    Description: A list of the public subnets
    Value: !Join [",", [!Ref PublicSubnet1, !Ref PublicSubnet2]]
    Export:
      Name: !Sub '${ProjectName}-${Environment}-PublicSubnets'

  PrivateSubnets:
    Description: A list of the private subnets
    Value: !Join [",", [!Ref PrivateSubnet1, !Ref PrivateSubnet2]]
    Export:
      Name: !Sub '${ProjectName}-${Environment}-PrivateSubnets'

  DBSubnets:
    Description: A list of the database subnets
    Value: !Join [",", [!Ref DBSubnet1, !Ref DBSubnet2]]
    Export:
      Name: !Sub '${ProjectName}-${Environment}-DBSubnets'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2: Deploy the VPC&lt;/p&gt;

&lt;p&gt;`# Deploy the VPC stack&lt;br&gt;
aws cloudformation create-stack \&lt;br&gt;
  --stack-name ecommerce-vpc \&lt;br&gt;
  --template-body file://cloudformation/network/vpc-base.yaml \&lt;br&gt;
  --parameters ParameterKey=ProjectName,ParameterValue=ecommerce \&lt;br&gt;
               ParameterKey=Environment,ParameterValue=production&lt;/p&gt;
&lt;h1&gt;
  
  
  Wait for stack completion
&lt;/h1&gt;

&lt;p&gt;aws cloudformation wait stack-create-complete --stack-name ecommerce-vpc&lt;/p&gt;
&lt;h1&gt;
  
  
  Verify the stack
&lt;/h1&gt;

&lt;p&gt;aws cloudformation describe-stacks --stack-name ecommerce-vpc`&lt;/p&gt;

&lt;p&gt;Security Groups Configuration&lt;br&gt;
Step 1: Create Security Groups Template&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bashtouch cloudformation/security/security-groups.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;File: cloudformation/security/security-groups.yaml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWSTemplateFormatVersion: '2010-09-09'
Description: 'Security Groups for E-commerce Application'

Parameters:
  ProjectName:
    Type: String
    Default: ecommerce
  Environment:
    Type: String
    Default: production

Resources:
  # ALB Security Group
  ALBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub '${ProjectName}-${Environment}-alb-sg'
      GroupDescription: Security group for Application Load Balancer
      VpcId: 
        Fn::ImportValue: !Sub '${ProjectName}-${Environment}-VPC'
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
          Description: 'HTTP from anywhere'
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
          Description: 'HTTPS from anywhere'
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-alb-sg'

  # ECS Security Group
  ECSSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub '${ProjectName}-${Environment}-ecs-sg'
      GroupDescription: Security group for ECS tasks
      VpcId: 
        Fn::ImportValue: !Sub '${ProjectName}-${Environment}-VPC'
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3000
          ToPort: 3000
          SourceSecurityGroupId: !Ref ALBSecurityGroup
          Description: 'HTTP from ALB'
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-ecs-sg'

  # RDS Security Group
  RDSSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub '${ProjectName}-${Environment}-rds-sg'
      GroupDescription: Security group for RDS database
      VpcId: 
        Fn::ImportValue: !Sub '${ProjectName}-${Environment}-VPC'
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 5432
          ToPort: 5432
          SourceSecurityGroupId: !Ref ECSSecurityGroup
          Description: 'PostgreSQL from ECS'
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-rds-sg'

  # Bastion Host Security Group (for debugging)
  BastionSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub '${ProjectName}-${Environment}-bastion-sg'
      GroupDescription: Security group for bastion host
      VpcId: 
        Fn::ImportValue: !Sub '${ProjectName}-${Environment}-VPC'
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0  # Restrict this to your IP in production
          Description: 'SSH access'
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-bastion-sg'

Outputs:
  ALBSecurityGroup:
    Description: Security group for ALB
    Value: !Ref ALBSecurityGroup
    Export:
      Name: !Sub '${ProjectName}-${Environment}-ALB-SG'

  ECSSecurityGroup:
    Description: Security group for ECS
    Value: !Ref ECSSecurityGroup
    Export:
      Name: !Sub '${ProjectName}-${Environment}-ECS-SG'

  RDSSecurityGroup:
    Description: Security group for RDS
    Value: !Ref RDSSecurityGroup
    Export:
      Name: !Sub '${ProjectName}-${Environment}-RDS-SG'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2: Deploy Security Groups&lt;/p&gt;

&lt;p&gt;`# Deploy security groups&lt;br&gt;
aws cloudformation create-stack \&lt;br&gt;
  --stack-name ecommerce-security-groups \&lt;br&gt;
  --template-body file://cloudformation/security/security-groups.yaml \&lt;br&gt;
  --parameters ParameterKey=ProjectName,ParameterValue=ecommerce \&lt;br&gt;
               ParameterKey=Environment,ParameterValue=production&lt;/p&gt;
&lt;h1&gt;
  
  
  Wait for completion
&lt;/h1&gt;

&lt;p&gt;aws cloudformation wait stack-create-complete --stack-name ecommerce-security-groups`&lt;/p&gt;

&lt;p&gt;IAM Roles and Policies&lt;br&gt;
Step 1: Create IAM Template&lt;/p&gt;

&lt;p&gt;&lt;code&gt;touch cloudformation/security/iam-roles.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;File: cloudformation/security/iam-roles.yaml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWSTemplateFormatVersion: '2010-09-09'
Description: 'IAM Roles and Policies for E-commerce Application'

Resources:
  # ECS Task Execution Role
  ECSTaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: ecommerce-ecs-task-execution-role
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
      Policies:
        - PolicyName: SecretsManagerAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - secretsmanager:GetSecretValue
                Resource: !Ref DBPasswordSecret

  # ECS Task Role
  ECSTaskRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: ecommerce-ecs-task-role
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: S3Access
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:PutObject
                  - s3:DeleteObject
                Resource:
                  - arn:aws:s3:::ecommerce-product-images/*
                  - arn:aws:s3:::ecommerce-user-uploads/*
        - PolicyName: CloudWatchLogs
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: '*'

  # CodePipeline Service Role
  CodePipelineServiceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: ecommerce-codepipeline-role
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: CodePipelinePolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetBucketVersioning
                  - s3:GetObject
                  - s3:GetObjectVersion
                  - s3:PutObject
                Resource: '*'
              - Effect: Allow
                Action:
                  - codebuild:BatchGetBuilds
                  - codebuild:StartBuild
                Resource: '*'
              - Effect: Allow
                Action:
                  - codedeploy:CreateDeployment
                  - codedeploy:GetApplication
                  - codedeploy:GetApplicationRevision
                  - codedeploy:GetDeployment
                  - codedeploy:GetDeploymentConfig
                  - codedeploy:RegisterApplicationRevision
                Resource: '*'

  # Database Password Secret
  DBPasswordSecret:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: ecommerce-db-password
      Description: Password for RDS PostgreSQL database
      GenerateSecretString:
        SecretStringTemplate: '{"username": "postgres"}'
        GenerateStringKey: 'password'
        PasswordLength: 32
        ExcludeCharacters: '"@/\'

Outputs:
  ECSTaskExecutionRoleArn:
    Description: ARN of the ECS Task Execution Role
    Value: !GetAtt ECSTaskExecutionRole.Arn
    Export:
      Name: ecommerce-production-ECS-TaskExecutionRole-Arn

  ECSTaskRoleArn:
    Description: ARN of the ECS Task Role
    Value: !GetAtt ECSTaskRole.Arn
    Export:
      Name: ecommerce-production-ECS-TaskRole-Arn

  DBPasswordSecretArn:
    Description: ARN of the database password secret
    Value: !Ref DBPasswordSecret
    Export:
      Name: ecommerce-production-DB-PasswordSecret-Arn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2: Deploy IAM Stack&lt;/p&gt;

&lt;p&gt;`# Deploy IAM roles&lt;br&gt;
aws cloudformation create-stack \&lt;br&gt;
  --stack-name ecommerce-iam-roles \&lt;br&gt;
  --template-body file://cloudformation/security/iam-roles.yaml \&lt;br&gt;
  --capabilities CAPABILITY_NAMED_IAM&lt;/p&gt;
&lt;h1&gt;
  
  
  Wait for completion
&lt;/h1&gt;

&lt;p&gt;aws cloudformation wait stack-create-complete --stack-name ecommerce-iam-roles`&lt;/p&gt;

&lt;p&gt;Create Sample Application&lt;br&gt;
Step 1: Create Backend Application&lt;/p&gt;

&lt;p&gt;`mkdir -p application/backend&lt;br&gt;
cd application/backend&lt;/p&gt;
&lt;h1&gt;
  
  
  Initialize Node.js project
&lt;/h1&gt;

&lt;p&gt;npm init -y&lt;/p&gt;
&lt;h1&gt;
  
  
  Install dependencies
&lt;/h1&gt;

&lt;p&gt;npm install express cors helmet morgan dotenv pg&lt;br&gt;
npm install -D nodemon&lt;/p&gt;
&lt;h1&gt;
  
  
  Create package.json scripts`
&lt;/h1&gt;

&lt;p&gt;File: application/backend/package.json&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "ecommerce-api",
  "version": "1.0.0",
  "description": "E-commerce API backend",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js",
    "test": "echo \"Error: no test specified\" &amp;amp;&amp;amp; exit 1"
  },
  "dependencies": {
    "express": "^4.18.2",
    "cors": "^2.8.5",
    "helmet": "^7.0.0",
    "morgan": "^1.10.0",
    "dotenv": "^16.3.1",
    "pg": "^8.11.3"
  },
  "devDependencies": {
    "nodemon": "^3.0.1"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;File: application/backend/server.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
require('dotenv').config();

const app = express();
const PORT = process.env.PORT || 3000;

// Middleware
app.use(helmet());
app.use(cors());
app.use(morgan('combined'));
app.use(express.json());

// Health check endpoint
app.get('/health', (req, res) =&amp;gt; {
  res.status(200).json({
    status: 'healthy',
    timestamp: new Date().toISOString(),
    uptime: process.uptime()
  });
});

// API routes
app.get('/api/products', (req, res) =&amp;gt; {
  res.json({
    products: [
      { id: 1, name: 'Laptop', price: 999.99, category: 'Electronics' },
      { id: 2, name: 'Smartphone', price: 699.99, category: 'Electronics' },
      { id: 3, name: 'Headphones', price: 199.99, category: 'Audio' }
    ]
  });
});

app.get('/api/products/:id', (req, res) =&amp;gt; {
  const productId = parseInt(req.params.id);
  const product = {
    id: productId,
    name: 'Sample Product',
    price: 99.99,
    description: 'This is a sample product',
    category: 'Sample Category'
  };
  res.json(product);
});

// Error handling middleware
app.use((err, req, res, next) =&amp;gt; {
  console.error(err.stack);
  res.status(500).json({ error: 'Something went wrong!' });
});

// 404 handler
app.use('*', (req, res) =&amp;gt; {
  res.status(404).json({ error: 'Route not found' });
});

app.listen(PORT, () =&amp;gt; {
  console.log(`Server running on port ${PORT}`);
  console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2: Create Dockerfile&lt;br&gt;
File: application/backend/Dockerfile&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM node:18-alpine

# Create app directory
WORKDIR /usr/src/app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy source code
COPY . .

# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001

# Change ownership to nodejs user
RUN chown -R nodejs:nodejs /usr/src/app
USER nodejs

# Expose port
EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node healthcheck.js

CMD ["npm", "start"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;File: application/backend/healthcheck.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const http = require('http');

const options = {
  hostname: 'localhost',
  port: 3000,
  path: '/health',
  method: 'GET'
};

const req = http.request(options, (res) =&amp;gt; {
  if (res.statusCode === 200) {
    process.exit(0);
  } else {
    process.exit(1);
  }
});

req.on('error', () =&amp;gt; {
  process.exit(1);
});

req.end();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 3: Test Locally&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7efj5vckiep46h4uhj5h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7efj5vckiep46h4uhj5h.png" alt=" " width="800" height="401"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Test the application locally
cd application/backend
npm install
npm run dev

# In another terminal, test the API
curl http://localhost:3000/health
curl http://localhost:3000/api/products

# Build and test Docker image
docker build -t ecommerce-api .
docker run -p 3000:3000 ecommerce-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiezvk5c54ovpd4af3ssa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiezvk5c54ovpd4af3ssa.png" alt=" " width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the out come of the build and test docker image.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwivg4zjbicdwjchn4b5u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwivg4zjbicdwjchn4b5u.png" alt=" " width="800" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ECR Setup and Container Push&lt;br&gt;
Step 1: Create ECR Repository&lt;/p&gt;

&lt;p&gt;`# Create ECR repository&lt;br&gt;
aws ecr create-repository \&lt;br&gt;
  --repository-name ecommerce-api \&lt;br&gt;
  --region us-east-1&lt;/p&gt;
&lt;h1&gt;
  
  
  Get login token. We will edit the command by copy and past our AWS account ID
&lt;/h1&gt;

&lt;p&gt;aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin .dkr.ecr.us-east-1.amazonaws.com`&lt;/p&gt;

&lt;p&gt;Step 2: Build and Push Image&lt;/p&gt;

&lt;p&gt;`cd application/backend&lt;/p&gt;
&lt;h1&gt;
  
  
  Build image
&lt;/h1&gt;

&lt;p&gt;docker build -t ecommerce-api .&lt;/p&gt;
&lt;h1&gt;
  
  
  We have to edit the command by copy and past our AWS account ID
&lt;/h1&gt;

&lt;p&gt;docker tag ecommerce-api:latest .dkr.ecr.us-east-1.amazonaws.com/ecommerce-api:latest&lt;/p&gt;
&lt;h1&gt;
  
  
  Push image
&lt;/h1&gt;

&lt;p&gt;docker push .dkr.ecr.us-east-1.amazonaws.com/ecommerce-api:latest`&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn5trql4uzdxscxk5ch5n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn5trql4uzdxscxk5ch5n.png" alt=" " width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step 3: Create Build Script&lt;br&gt;
File: scripts/build-and-push.sh&lt;/p&gt;

&lt;p&gt;`#!/bin/bash&lt;/p&gt;
&lt;h1&gt;
  
  
  Build and push script
&lt;/h1&gt;

&lt;p&gt;set -e&lt;/p&gt;
&lt;h1&gt;
  
  
  Variables
&lt;/h1&gt;

&lt;p&gt;AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)&lt;br&gt;
AWS_REGION=us-east-1&lt;br&gt;
IMAGE_NAME=ecommerce-api&lt;br&gt;
IMAGE_TAG=${1:-latest}&lt;/p&gt;
&lt;h1&gt;
  
  
  Login to ECR
&lt;/h1&gt;

&lt;p&gt;aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com&lt;/p&gt;
&lt;h1&gt;
  
  
  Build image
&lt;/h1&gt;

&lt;p&gt;echo "Building Docker image..."&lt;br&gt;
docker build -t $IMAGE_NAME:$IMAGE_TAG application/backend/&lt;/p&gt;
&lt;h1&gt;
  
  
  Tag image
&lt;/h1&gt;

&lt;p&gt;docker tag $IMAGE_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$IMAGE_TAG&lt;/p&gt;
&lt;h1&gt;
  
  
  Push image
&lt;/h1&gt;

&lt;p&gt;echo "Pushing to ECR..."&lt;br&gt;
docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$IMAGE_TAG&lt;/p&gt;

&lt;p&gt;echo "Build and push completed successfully!"`&lt;/p&gt;

&lt;p&gt;`# Make script executable&lt;br&gt;
chmod +x scripts/build-and-push.sh&lt;/p&gt;
&lt;h1&gt;
  
  
  Run the script
&lt;/h1&gt;

&lt;p&gt;./scripts/build-and-push.sh`&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff2kb0q46dvdmk7h1kj86.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff2kb0q46dvdmk7h1kj86.png" alt=" " width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What You've Accomplished:&lt;br&gt;
✅ VPC Infrastructure: Multi-tier network with proper subnet isolation&lt;br&gt;
✅ Security Groups: Layered security with least privilege access&lt;br&gt;
✅ IAM Roles: Secure role-based access control&lt;br&gt;
✅ Sample Application: Containerized Node.js API&lt;br&gt;
✅ ECR Repository: Container registry with pushed image&lt;br&gt;
Verification Commands:&lt;/p&gt;

&lt;p&gt;`# Check all your stacks&lt;br&gt;
aws cloudformation list-stacks --stack-status-filter CREATE_COMPLETE&lt;/p&gt;
&lt;h1&gt;
  
  
  Verify VPC resources
&lt;/h1&gt;

&lt;p&gt;aws ec2 describe-vpcs --filters "Name=tag:Name,Values=ecommerce-production-vpc"&lt;br&gt;
aws ec2 describe-subnets --filters "Name=vpc-id,Values="&lt;/p&gt;
&lt;h1&gt;
  
  
  Check ECR image
&lt;/h1&gt;

&lt;p&gt;aws ecr describe-images --repository-name ecommerce-api`&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg1jiitroqscdpzlh5cuh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg1jiitroqscdpzlh5cuh.png" alt=" " width="800" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqukf2xj45zoizqxzs8u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqukf2xj45zoizqxzs8u.png" alt=" " width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq9pcocatee5jolf9fimd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq9pcocatee5jolf9fimd.png" alt=" " width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;File: documentation/week1-progress.md&lt;/p&gt;

&lt;p&gt;`#Check our Progress Report&lt;/p&gt;
&lt;h2&gt;
  
  
  Infrastructure Completed
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[x] VPC with 6 subnets across 2 AZs&lt;/li&gt;
&lt;li&gt;[x] NAT Gateways for high availability&lt;/li&gt;
&lt;li&gt;[x] Security groups with least privilege access&lt;/li&gt;
&lt;li&gt;[x] IAM roles for ECS and CI/CD&lt;/li&gt;
&lt;li&gt;[x] Containerized Node.js application&lt;/li&gt;
&lt;li&gt;[x] ECR repository with pushed image&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Architecture Decisions
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Multi-AZ Design&lt;/strong&gt;: Ensures high availability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private Subnets&lt;/strong&gt;: Applications run in private subnets for security&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Defense in Depth&lt;/strong&gt;: Multiple security layers (NACLs, Security Groups, IAM)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Least Privilege&lt;/strong&gt;: IAM policies grant minimal required permissions
`
Application Infrastructure &amp;amp; Database
Day 6: RDS Database Setup
Step 1: Create RDS Template&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;touch cloudformation/storage/rds-database.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;File: cloudformation/storage/rds-database.yaml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWSTemplateFormatVersion: '2010-09-09'
Description: 'RDS PostgreSQL Database for E-commerce Application'

Parameters:
  ProjectName:
    Type: String
    Default: ecommerce
  Environment:
    Type: String
    Default: production
  DBInstanceClass:
    Type: String
    Default: db.t3.micro
    Description: RDS instance class
    AllowedValues:
      - db.t3.micro
      - db.t3.small
      - db.t3.medium
      - db.t3.large
  DBPassword:
    Type: String
    NoEcho: true
    Description: Master password for RDS instance (8-128 characters)
    MinLength: 8
    MaxLength: 128
    AllowedPattern: ^[a-zA-Z0-9!@#$%^&amp;amp;*()_+=-]*$
    ConstraintDescription: Must contain 8-128 alphanumeric characters

Resources:
  # DB Subnet Group
  DBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupName: !Sub '${ProjectName}-${Environment}-db-subnet-group'
      DBSubnetGroupDescription: Subnet group for RDS database
      SubnetIds: !Split 
        - ','
        - Fn::ImportValue: !Sub '${ProjectName}-${Environment}-DBSubnets'
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-db-subnet-group'

  # RDS Instance
  RDSInstance:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: Snapshot
    Properties:
      DBInstanceIdentifier: !Sub '${ProjectName}-${Environment}-postgres'
      DBInstanceClass: !Ref DBInstanceClass
      Engine: postgres
      # EngineVersion: '15.4'  # Comment out to use default version
      AllocatedStorage: 20
      StorageType: gp2
      StorageEncrypted: true

      DBName: ecommerce
      MasterUsername: postgres
      MasterUserPassword: !Ref DBPassword  # Using parameter instead of Secrets Manager

      VPCSecurityGroups:
        - Fn::ImportValue: !Sub '${ProjectName}-${Environment}-RDS-SG'
      DBSubnetGroupName: !Ref DBSubnetGroup

      BackupRetentionPeriod: 7
      PreferredBackupWindow: "03:00-04:00"
      PreferredMaintenanceWindow: "sun:04:00-sun:05:00"

      MultiAZ: false  # Set to true for production
      PubliclyAccessible: false

      EnablePerformanceInsights: true
      PerformanceInsightsRetentionPeriod: 7

      DeletionProtection: false  # Set to true for production

      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-postgres'
        - Key: Environment
          Value: !Ref Environment

Outputs:
  RDSEndpoint:
    Description: RDS instance endpoint
    Value: !GetAtt RDSInstance.Endpoint.Address
    Export:
      Name: !Sub '${ProjectName}-${Environment}-RDS-Endpoint'

  RDSPort:
    Description: RDS instance port
    Value: !GetAtt RDSInstance.Endpoint.Port
    Export:
      Name: !Sub '${ProjectName}-${Environment}-RDS-Port'

  RDSInstanceId:
    Description: RDS instance identifier
    Value: !Ref RDSInstance
    Export:
      Name: !Sub '${ProjectName}-${Environment}-RDS-InstanceId'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2: Deploy RDS Stack&lt;/p&gt;

&lt;p&gt;`# Deploy RDS database&lt;br&gt;
aws cloudformation create-stack \&lt;br&gt;
  --stack-name ecommerce-rds \&lt;br&gt;
  --template-body file://cloudformation/storage/rds-database.yaml \&lt;br&gt;
  --parameters ParameterKey=DBPassword,ParameterValue=YourSecurePassword123!&lt;/p&gt;
&lt;h1&gt;
  
  
  This will take 10-15 minutes
&lt;/h1&gt;

&lt;p&gt;aws cloudformation wait stack-create-complete --stack-name ecommerce-rds`&lt;/p&gt;

&lt;p&gt;ECS Cluster Setup&lt;br&gt;
Step 1: Create ECS Template&lt;/p&gt;

&lt;p&gt;&lt;code&gt;touch cloudformation/compute/ecs-cluster.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;File: cloudformation/compute/ecs-cluster.yaml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWSTemplateFormatVersion: '2010-09-09'
Description: 'ECS Cluster and Service for E-commerce API'

Parameters:
  ProjectName:
    Type: String
    Default: ecommerce
  Environment:
    Type: String
    Default: production
  ImageURI:
    Type: String
    Description: ECR image URI
  DBEndpoint:
    Type: String
    Default: ''
    Description: RDS endpoint (leave empty to auto-import)

Conditions:
  HasDBEndpoint: !Not [!Equals [!Ref DBEndpoint, '']]

Resources:
  # ECS Cluster
  ECSCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Sub '${ProjectName}-${Environment}-cluster'
      ClusterSettings:
        - Name: containerInsights
          Value: enabled
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-cluster'

  # CloudWatch Log Group
  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/aws/ecs/${ProjectName}-${Environment}-api'
      RetentionInDays: 7

  # ECS Task Definition
  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: !Sub '${ProjectName}-${Environment}-api'
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      Cpu: '256'
      Memory: '512'
      ExecutionRoleArn: 
        Fn::ImportValue: !Sub '${ProjectName}-${Environment}-ECS-TaskExecutionRole-Arn'
      TaskRoleArn:
        Fn::ImportValue: !Sub '${ProjectName}-${Environment}-ECS-TaskRole-Arn'
      ContainerDefinitions:
        - Name: !Sub '${ProjectName}-api'
          Image: !Ref ImageURI
          PortMappings:
            - ContainerPort: 3000
              Protocol: tcp
          Environment:
            - Name: NODE_ENV
              Value: production
            - Name: PORT
              Value: '3000'
            - Name: DB_HOST
              Value: !If 
                - HasDBEndpoint
                - !Ref DBEndpoint
                - Fn::ImportValue: !Sub '${ProjectName}-${Environment}-RDS-Endpoint'
            - Name: DB_PORT
              Value: '5432'
            - Name: DB_NAME
              Value: ecommerce
            - Name: DB_USER
              Value: postgres
          Secrets:
            - Name: DB_PASSWORD
              ValueFrom: 
                Fn::ImportValue: !Sub '${ProjectName}-${Environment}-DB-PasswordSecret-Arn'
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: ecs
          # Simplified health check - using TCP instead of HTTP
          # Remove if your container doesn't support health checks
          Essential: true

  # Application Load Balancer
  ApplicationLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Sub '${ProjectName}-${Environment}-alb'
      Scheme: internet-facing
      Type: application
      SecurityGroups:
        - Fn::ImportValue: !Sub '${ProjectName}-${Environment}-ALB-SG'
      Subnets: !Split
        - ','
        - Fn::ImportValue: !Sub '${ProjectName}-${Environment}-PublicSubnets'
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-alb'

  # Target Group
  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub '${ProjectName}-${Environment}-tg'
      Port: 3000
      Protocol: HTTP
      VpcId:
        Fn::ImportValue: !Sub '${ProjectName}-${Environment}-VPC'
      TargetType: ip
      HealthCheckIntervalSeconds: 30
      HealthCheckPath: /health
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      UnhealthyThresholdCount: 3
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: '30'

  # ALB Listener - Fixed syntax
  ALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          ForwardConfig:
            TargetGroups:
              - TargetGroupArn: !Ref TargetGroup
                Weight: 100
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: 80
      Protocol: HTTP

  # ECS Service
  ECSService:
    Type: AWS::ECS::Service
    DependsOn: ALBListener
    Properties:
      ServiceName: !Sub '${ProjectName}-${Environment}-api-service'
      Cluster: !Ref ECSCluster
      TaskDefinition: !Ref TaskDefinition
      LaunchType: FARGATE
      DesiredCount: 2
      NetworkConfiguration:
        AwsvpcConfiguration:
          SecurityGroups:
            - Fn::ImportValue: !Sub '${ProjectName}-${Environment}-ECS-SG'
          Subnets: !Split
            - ','
            - Fn::ImportValue: !Sub '${ProjectName}-${Environment}-PrivateSubnets'
          AssignPublicIp: DISABLED
      LoadBalancers:
        - ContainerName: !Sub '${ProjectName}-api'
          ContainerPort: 3000
          TargetGroupArn: !Ref TargetGroup
      HealthCheckGracePeriodSeconds: 120
      DeploymentConfiguration:
        MaximumPercent: 200
        MinimumHealthyPercent: 50
        DeploymentCircuitBreaker:
          Enable: true
          Rollback: true

Outputs:
  ClusterName:
    Description: ECS Cluster Name
    Value: !Ref ECSCluster
    Export:
      Name: !Sub '${ProjectName}-${Environment}-ECS-Cluster'

  ServiceName:
    Description: ECS Service Name
    Value: !Ref ECSService
    Export:
      Name: !Sub '${ProjectName}-${Environment}-ECS-Service'

  LoadBalancerDNS:
    Description: Load Balancer DNS Name
    Value: !GetAtt ApplicationLoadBalancer.DNSName
    Export:
      Name: !Sub '${ProjectName}-${Environment}-ALB-DNS'

  LoadBalancerArn:
    Description: Load Balancer ARN
    Value: !Ref ApplicationLoadBalancer
    Export:
      Name: !Sub '${ProjectName}-${Environment}-ALB-Arn'

  LoadBalancerURL:
    Description: Load Balancer URL
    Value: !Sub 'http://${ApplicationLoadBalancer.DNSName}'
    Export:
      Name: !Sub '${ProjectName}-${Environment}-ALB-URL'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before Deploying, Validate:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;aws cloudformation validate-template --template-body file://cloudformation/compute/ecs-cluster.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;# Check if these exports exist from your other stacks:&lt;br&gt;
aws cloudformation list-exports --query 'Exports[?contains(Name,&lt;/code&gt;ecommerce-production&lt;code&gt;)].Name'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;`# Get your AWS account ID&lt;br&gt;
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)&lt;/p&gt;
&lt;h1&gt;
  
  
  Set image URI
&lt;/h1&gt;

&lt;p&gt;IMAGE_URI="$AWS_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/ecommerce-api:latest"&lt;/p&gt;
&lt;h1&gt;
  
  
  Deploy ECS stack
&lt;/h1&gt;

&lt;p&gt;aws cloudformation create-stack \&lt;br&gt;
  --stack-name ecommerce-ecs \&lt;br&gt;
  --template-body file://cloudformation/compute/ecs-cluster.yaml \&lt;br&gt;
  --parameters ParameterKey=ProjectName,ParameterValue=ecommerce \&lt;br&gt;
               ParameterKey=Environment,ParameterValue=production \&lt;br&gt;
               ParameterKey=ImageURI,ParameterValue=$IMAGE_URI&lt;/p&gt;
&lt;h1&gt;
  
  
  This will take 10-15 minutes
&lt;/h1&gt;

&lt;p&gt;aws cloudformation wait stack-create-complete --stack-name ecommerce-ecs`&lt;/p&gt;

&lt;p&gt;Day 8: S3 Buckets Setup&lt;br&gt;
Step 1: Create S3 Template&lt;/p&gt;

&lt;p&gt;&lt;code&gt;touch cloudformation/storage/s3-buckets.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;File: cloudformation/storage/s3-buckets.yaml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWSTemplateFormatVersion: '2010-09-09'
Description: 'S3 Buckets for E-commerce Application'

Parameters:
  ProjectName:
    Type: String
    Default: ecommerce
  Environment:
    Type: String
    Default: production
  AllowedOrigins:
    Type: String
    Default: 'https://example.com,https://www.example.com'
    Description: Allowed origins for CORS (comma-separated)

Resources:
  # KMS Key for S3 Encryption
  S3KMSKey:
    Type: AWS::KMS::Key
    Properties:
      Description: KMS Key for S3 bucket encryption
      KeyPolicy:
        Version: '2012-10-17'
        Statement:
          - Sid: Enable IAM User Permissions
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
            Action: 'kms:*'
            Resource: '*'
          - Sid: Allow S3 Service
            Effect: Allow
            Principal:
              Service: s3.amazonaws.com
            Action:
              - kms:Decrypt
              - kms:GenerateDataKey
            Resource: '*'

  S3KMSKeyAlias:
    Type: AWS::KMS::Alias
    Properties:
      AliasName: !Sub 'alias/${ProjectName}-${Environment}-s3-key'
      TargetKeyId: !Ref S3KMSKey

  # CloudWatch Log Group for S3 (moved up for dependency order)
  S3LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/aws/s3/${ProjectName}-${Environment}'
      RetentionInDays: 14

  # Frontend Assets Bucket
  FrontendBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub '${ProjectName}-${Environment}-frontend-${AWS::AccountId}'
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      VersioningConfiguration:
        Status: Enabled
      LifecycleConfiguration:
        Rules:
          - Id: DeleteOldVersions
            Status: Enabled
            NoncurrentVersionExpirationInDays: 30
          - Id: DeleteIncompleteMultipartUploads
            Status: Enabled
            AbortIncompleteMultipartUpload:
              DaysAfterInitiation: 7
      # Removed invalid NotificationConfiguration
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-frontend'
        - Key: Purpose
          Value: 'Frontend static assets'

  # Product Images Bucket
  ProductImagesBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub '${ProjectName}-${Environment}-product-images-${AWS::AccountId}'
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: aws:kms
              KMSMasterKeyID: !Ref S3KMSKey
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      VersioningConfiguration:
        Status: Enabled
      CorsConfiguration:
        CorsRules:
          - AllowedHeaders: ['*']
            AllowedMethods: [GET, PUT, POST, DELETE]
            AllowedOrigins: !Ref AllowedOrigins
            MaxAge: 3600
      LifecycleConfiguration:
        Rules:
          - Id: TransitionToIA
            Status: Enabled
            Transitions:
              - Days: 30
                StorageClass: STANDARD_IA
          - Id: TransitionToGlacier
            Status: Enabled
            Transitions:
              - Days: 90
                StorageClass: GLACIER
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-product-images'

  # User Uploads Bucket
  UserUploadsBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub '${ProjectName}-${Environment}-user-uploads-${AWS::AccountId}'
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: aws:kms
              KMSMasterKeyID: !Ref S3KMSKey
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      VersioningConfiguration:
        Status: Enabled
      CorsConfiguration:
        CorsRules:
          - AllowedHeaders: ['*']
            AllowedMethods: [PUT, POST]
            AllowedOrigins: !Ref AllowedOrigins
            MaxAge: 3600
      LifecycleConfiguration:
        Rules:
          - Id: DeleteOldVersions
            Status: Enabled
            NoncurrentVersionExpirationInDays: 7
          - Id: DeleteOldUploads
            Status: Enabled
            ExpirationInDays: 365
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-user-uploads'

  # CloudFront Origin Access Control (replaces deprecated OAI)
  CloudFrontOAC:
    Type: AWS::CloudFront::OriginAccessControl
    Properties:
      OriginAccessControlConfig:
        Name: !Sub '${ProjectName}-${Environment}-oac'
        Description: !Sub 'OAC for ${ProjectName}-${Environment}'
        OriginAccessControlOriginType: s3
        SigningBehavior: always
        SigningProtocol: sigv4

  # Bucket Policy for CloudFront OAC
  FrontendBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref FrontendBucket
      PolicyDocument:
        Statement:
          - Sid: AllowCloudFrontServicePrincipal
            Effect: Allow
            Principal:
              Service: cloudfront.amazonaws.com
            Action: 's3:GetObject'
            Resource: !Sub '${FrontendBucket}/*'
            Condition:
              StringEquals:
                'AWS:SourceArn': !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/*'

Outputs:
  FrontendBucketName:
    Description: Name of the frontend assets bucket
    Value: !Ref FrontendBucket
    Export:
      Name: !Sub '${ProjectName}-${Environment}-Frontend-Bucket'

  ProductImagesBucketName:
    Description: Name of the product images bucket
    Value: !Ref ProductImagesBucket
    Export:
      Name: !Sub '${ProjectName}-${Environment}-ProductImages-Bucket'

  UserUploadsBucketName:
    Description: Name of the user uploads bucket
    Value: !Ref UserUploadsBucket
    Export:
      Name: !Sub '${ProjectName}-${Environment}-UserUploads-Bucket'

  CloudFrontOAC:
    Description: CloudFront Origin Access Control
    Value: !Ref CloudFrontOAC
    Export:
      Name: !Sub '${ProjectName}-${Environment}-CloudFront-OAC'

  FrontendBucketDomainName:
    Description: Frontend bucket domain name
    Value: !GetAtt FrontendBucket.DomainName
    Export:
      Name: !Sub '${ProjectName}-${Environment}-Frontend-Bucket-Domain'

  S3KMSKeyId:
    Description: KMS Key ID for S3 encryption
    Value: !Ref S3KMSKey
    Export:
      Name: !Sub '${ProjectName}-${Environment}-S3-KMS-Key'

  S3KMSKeyArn:
    Description: KMS Key ARN for S3 encryption
    Value: !GetAtt S3KMSKey.Arn
    Export:
      Name: !Sub '${ProjectName}-${Environment}-S3-KMS-Key-Arn'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validation Commands:&lt;/p&gt;

&lt;p&gt;`# Validate the template&lt;br&gt;
aws cloudformation validate-template --template-body file://cloudformation/storage/s3-buckets.yaml&lt;/p&gt;
&lt;h1&gt;
  
  
  Deploy with custom origins
&lt;/h1&gt;

&lt;p&gt;aws cloudformation create-stack \&lt;br&gt;
  --stack-name ecommerce-s3 \&lt;br&gt;
  --template-body file://cloudformation/storage/s3-buckets.yaml \&lt;br&gt;
  --parameters '[{"ParameterKey":"AllowedOrigins","ParameterValue":"&lt;a href="https://yourdomain.com,https://www.yourdomain.com%22%7D%5D'%60%7B%" rel="noopener noreferrer"&gt;https://yourdomain.com,https://www.yourdomain.com"}]'`{%&lt;/a&gt; endraw %}&lt;/p&gt;

&lt;p&gt;Next steps to monitor the stack:&lt;br&gt;
Check stack status:&lt;br&gt;
{% raw %}&lt;code&gt;aws cloudformation describe-stacks --stack-name ecommerce-s3&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Monitor stack events (see what's happening):&lt;br&gt;
&lt;code&gt;aws cloudformation describe-stack-events --stack-name ecommerce-s3&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Get stack resources (once complete):&lt;br&gt;
&lt;code&gt;aws cloudformation describe-stack-resources --stack-name ecommerce-s3&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Get stack outputs (if any are defined):&lt;br&gt;
&lt;code&gt;aws cloudformation describe-stacks --stack-name ecommerce-s3 --query 'Stacks[0].Outputs'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4l8nv2c9fkr5ocwvgo9n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4l8nv2c9fkr5ocwvgo9n.png" alt=" " width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;loudFront Distribution&lt;br&gt;
Step 1: Create CloudFront Template&lt;/p&gt;

&lt;p&gt;&lt;code&gt;touch cloudformation/storage/cloudfront-distribution.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;File: cloudformation/storage/cloudfront-distribution.yaml&lt;/p&gt;

&lt;p&gt;&lt;code&gt;# Or see all events in chronological order to understand the full timeline&lt;br&gt;
aws cloudformation describe-stack-events --stack-name ecommerce-cloudfront --query 'reverse(sort_by(StackEvents, &amp;amp;Timestamp))[0:30].[Timestamp,LogicalResourceId,ResourceStatus,ResourceStatusReason]' --output table&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbodxidbn1ye18tu7v9py.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbodxidbn1ye18tu7v9py.png" alt=" " width="800" height="314"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWSTemplateFormatVersion: '2010-09-09'
Description: 'CloudFront Distribution for E-commerce Application - Fully Fixed'

Parameters:
  ProjectName:
    Type: String
    Default: ecommerce
    Description: Project name prefix for resources
  Environment:
    Type: String
    Default: production
    AllowedValues: [production, staging, development]
    Description: Deployment environment

Resources:
  # S3 Bucket for Frontend (with proper ownership controls)
  FrontendBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub '${ProjectName}-${Environment}-frontend-${AWS::AccountId}'
      WebsiteConfiguration:
        IndexDocument: index.html
        ErrorDocument: error.html
      PublicAccessBlockConfiguration:
        BlockPublicAcls: false
        BlockPublicPolicy: false
        IgnorePublicAcls: false
        RestrictPublicBuckets: false
      OwnershipControls:
        Rules:
          - ObjectOwnership: BucketOwnerPreferred
      CorsConfiguration:
        CorsRules:
          - AllowedHeaders: ['*']
            AllowedMethods: [GET, HEAD]
            AllowedOrigins: ['*']
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-frontend'
        - Key: Environment
          Value: !Ref Environment

  # CloudFront Origin Access Control (OAC) with enhanced description
  CloudFrontOAC:
    Type: AWS::CloudFront::OriginAccessControl
    Properties:
      OriginAccessControlConfig:
        Name: !Sub '${ProjectName}-${Environment}-oac'
        OriginAccessControlOriginType: s3
        SigningBehavior: always
        SigningProtocol: sigv4
        Description: !Sub 'OAC for ${ProjectName}-${Environment} S3 bucket (Managed by CloudFormation)'

  # Logging bucket with all required configurations
  LoggingBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub '${ProjectName}-${Environment}-cf-logs-${AWS::AccountId}'
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      PublicAccessBlockConfiguration:
        BlockPublicAcls: false    # Required for CloudFront logging
        IgnorePublicAcls: false  # Required for CloudFront logging
        BlockPublicPolicy: true
        RestrictPublicBuckets: true
      OwnershipControls:
        Rules:
          - ObjectOwnership: BucketOwnerPreferred
      LifecycleConfiguration:
        Rules:
          - Id: DeleteOldLogs
            Status: Enabled
            ExpirationInDays: 90
            NoncurrentVersionExpirationInDays: 90
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-cf-logs'
        - Key: Environment
          Value: !Ref Environment

  # Security Headers Policy with XSS protection
  SecurityHeadersPolicy:
    Type: AWS::CloudFront::ResponseHeadersPolicy
    Properties:
      ResponseHeadersPolicyConfig:
        Name: !Sub '${ProjectName}-${Environment}-security-headers'
        Comment: Security headers for e-commerce application
        SecurityHeadersConfig:
          StrictTransportSecurity:
            AccessControlMaxAgeSec: 31536000
            IncludeSubdomains: true
            Override: true
          ContentTypeOptions:
            Override: true
          FrameOptions:
            FrameOption: DENY
            Override: true
          ReferrerPolicy:
            ReferrerPolicy: strict-origin-when-cross-origin
            Override: true

        CustomHeadersConfig:
          Items:
            - Header: X-Custom-Header
              Value: !Sub '${ProjectName}-${Environment}'
              Override: false

  # CloudFront Distribution with all fixes
  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    DependsOn: 
      - SecurityHeadersPolicy
      - CloudFrontOAC
      - LoggingBucket
      - FrontendBucket
    Properties:
      DistributionConfig:
        Origins:
          # S3 Origin
          - DomainName: !GetAtt FrontendBucket.DomainName
            Id: S3Origin
            OriginAccessControlId: !Ref CloudFrontOAC
            S3OriginConfig: {}

          # ALB Origin with validation
          - DomainName: 
              Fn::ImportValue: !Sub '${ProjectName}-${Environment}-ALB-DNS'
            Id: ALBOrigin
            CustomOriginConfig:
              HTTPPort: 80
              HTTPSPort: 443
              OriginProtocolPolicy: http-only
              OriginSSLProtocols: [TLSv1.2]
              OriginReadTimeout: 30
              OriginKeepaliveTimeout: 5

        DefaultCacheBehavior:
          TargetOriginId: S3Origin
          ViewerProtocolPolicy: redirect-to-https
          CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad  # CachingOptimized
          OriginRequestPolicyId: 88a5eaf4-2fd4-4709-b370-b4c650ea3fcf  # CORS-S3Origin
          ResponseHeadersPolicyId: !Ref SecurityHeadersPolicy
          Compress: true
          SmoothStreaming: false
          AllowedMethods: [GET, HEAD, OPTIONS]
          CachedMethods: [GET, HEAD, OPTIONS]

        CacheBehaviors:
          # API Path
          - PathPattern: '/api/*'
            TargetOriginId: ALBOrigin
            ViewerProtocolPolicy: redirect-to-https
            CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
            OriginRequestPolicyId: 216adef6-5c7f-47e4-b989-5492eafa07d3  # AllViewer
            ResponseHeadersPolicyId: !Ref SecurityHeadersPolicy
            AllowedMethods: [GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE]
            CachedMethods: [GET, HEAD, OPTIONS]

          # Static Assets Path
          - PathPattern: '/static/*'
            TargetOriginId: S3Origin
            ViewerProtocolPolicy: redirect-to-https
            CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6  # CachingOptimized
            OriginRequestPolicyId: 88a5eaf4-2fd4-4709-b370-b4c650ea3fcf
            ResponseHeadersPolicyId: !Ref SecurityHeadersPolicy
            Compress: true
            AllowedMethods: [GET, HEAD, OPTIONS]
            CachedMethods: [GET, HEAD, OPTIONS]

        Enabled: true
        DefaultRootObject: index.html
        HttpVersion: http2
        IPV6Enabled: true

        CustomErrorResponses:
          - ErrorCode: 403
            ResponseCode: 200
            ResponsePagePath: /index.html
            ErrorCachingMinTTL: 300
          - ErrorCode: 404
            ResponseCode: 200
            ResponsePagePath: /index.html
            ErrorCachingMinTTL: 300

        PriceClass: PriceClass_100

        ViewerCertificate:
          CloudFrontDefaultCertificate: true

        Logging:
          Bucket: !GetAtt LoggingBucket.DomainName
          IncludeCookies: false
          Prefix: cloudfront-logs/

        Comment: !Sub 'CloudFront distribution for ${ProjectName}-${Environment}'

      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-cloudfront'
        - Key: Environment
          Value: !Ref Environment

  # S3 Bucket Policy with proper dependencies
  FrontendBucketPolicy:
    Type: AWS::S3::BucketPolicy
    DependsOn: 
      - CloudFrontDistribution
      - FrontendBucket
    Properties:
      Bucket: !Ref FrontendBucket
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: cloudfront.amazonaws.com
            Action: s3:GetObject
            Resource: !Sub '${FrontendBucket.Arn}/*'
            Condition:
              StringEquals:
                'AWS:SourceArn': !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}'

  # CloudWatch Log Group with retention policy
  CloudFrontLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/aws/cloudfront/${ProjectName}-${Environment}'
      RetentionInDays: 30
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-cf-logs'
        - Key: Environment
          Value: !Ref Environment

  # SNS Topic with proper naming
  SNSAlarmTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: !Sub '${ProjectName}-${Environment}-cloudfront-alarms'
      DisplayName: !Sub '${ProjectName}-${Environment} CloudFront Alerts'
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-${Environment}-cf-alerts'
        - Key: Environment
          Value: !Ref Environment

  # Enhanced CloudWatch Alarms
  HighErrorRateAlarm:
    Type: AWS::CloudWatch::Alarm
    DependsOn: 
      - CloudFrontDistribution
      - SNSAlarmTopic
    Properties:
      AlarmName: !Sub '${ProjectName}-${Environment}-cloudfront-high-error-rate'
      AlarmDescription: CloudFront high 4xx/5xx error rate (&amp;gt;5% for 15 minutes)
      Namespace: AWS/CloudFront
      MetricName: 4xxErrorRate
      Dimensions:
        - Name: DistributionId
          Value: !Ref CloudFrontDistribution
        - Name: Region
          Value: Global
      Statistic: Average
      Period: 300
      EvaluationPeriods: 3
      Threshold: 5
      ComparisonOperator: GreaterThanThreshold
      TreatMissingData: notBreaching
      AlarmActions:
        - !Ref SNSAlarmTopic
      OKActions:
        - !Ref SNSAlarmTopic

  HighOriginLatencyAlarm:
    Type: AWS::CloudWatch::Alarm
    DependsOn: 
      - CloudFrontDistribution
      - SNSAlarmTopic
    Properties:
      AlarmName: !Sub '${ProjectName}-${Environment}-cloudfront-high-origin-latency'
      AlarmDescription: CloudFront origin latency &amp;gt;3s for 15 minutes
      Namespace: AWS/CloudFront
      MetricName: OriginLatency
      Dimensions:
        - Name: DistributionId
          Value: !Ref CloudFrontDistribution
        - Name: Region
          Value: Global
      Statistic: Average
      Period: 300
      EvaluationPeriods: 3
      Threshold: 3000
      ComparisonOperator: GreaterThanThreshold
      TreatMissingData: notBreaching
      AlarmActions:
        - !Ref SNSAlarmTopic
      OKActions:
        - !Ref SNSAlarmTopic

Outputs:
  CloudFrontDistributionId:
    Description: CloudFront Distribution ID
    Value: !Ref CloudFrontDistribution
    Export:
      Name: !Sub '${ProjectName}-${Environment}-CloudFront-DistributionId'

  CloudFrontDomainName:
    Description: CloudFront Distribution Domain Name
    Value: !GetAtt CloudFrontDistribution.DomainName
    Export:
      Name: !Sub '${ProjectName}-${Environment}-CloudFront-DomainName'

  CloudFrontDistributionURL:
    Description: CloudFront Distribution URL
    Value: !Sub 'https://${CloudFrontDistribution.DomainName}'
    Export:
      Name: !Sub '${ProjectName}-${Environment}-CloudFront-URL'

  FrontendBucketName:
    Description: Frontend S3 Bucket Name
    Value: !Ref FrontendBucket
    Export:
      Name: !Sub '${ProjectName}-${Environment}-Frontend-Bucket-Name'

  FrontendWebsiteURL:
    Description: Frontend Website URL
    Value: !GetAtt FrontendBucket.WebsiteURL
    Export:
      Name: !Sub '${ProjectName}-${Environment}-Frontend-Website-URL'

  LoggingBucketName:
    Description: CloudFront Logging Bucket Name
    Value: !Ref LoggingBucket
    Export:
      Name: !Sub '${ProjectName}-${Environment}-Logging-Bucket-Name'

  SecurityHeadersPolicyId:
    Description: Security Headers Policy ID
    Value: !Ref SecurityHeadersPolicy
    Export:
      Name: !Sub '${ProjectName}-${Environment}-Security-Headers-Policy'

  SNSAlarmTopicARN:
    Description: SNS Alarm Topic ARN
    Value: !Ref SNSAlarmTopic
    Export:
      Name: !Sub '${ProjectName}-${Environment}-SNS-Alarm-Topic'

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2: Deploy CloudFront&lt;/p&gt;

&lt;p&gt;`# Deploy CloudFront distribution&lt;br&gt;
aws cloudformation create-stack \&lt;br&gt;
  --stack-name ecommerce-cloudfront \&lt;br&gt;
  --template-body file://cloudformation/storage/cloudfront-distribution.yaml \&lt;br&gt;
  --parameters ParameterKey=ProjectName,ParameterValue=ecommerce \&lt;br&gt;
               ParameterKey=Environment,ParameterValue=production&lt;/p&gt;
&lt;h1&gt;
  
  
  This takes 15-20 minutes
&lt;/h1&gt;

&lt;p&gt;aws cloudformation wait stack-create-complete --stack-name ecommerce-cloudfront`&lt;/p&gt;

&lt;p&gt;Testing and Verification&lt;br&gt;
Step 1: Create Test Scripts&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mkdir -p scripts/testing&lt;br&gt;
touch scripts/testing/test-infrastructure.sh&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;File: scripts/testing/test-infrastructure.sh&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

set -e

echo "🔍 Testing AWS E-commerce Infrastructure..."

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# Test functions
test_vpc() {
    echo -e "${YELLOW}Testing VPC...${NC}"
    VPC_ID=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=ecommerce-production-vpc" --query 'Vpcs[0].VpcId' --output text)
    if [ "$VPC_ID" != "None" ] &amp;amp;&amp;amp; [ "$VPC_ID" != "" ]; then
        echo -e "${GREEN}✓ VPC exists: $VPC_ID${NC}"
    else
        echo -e "${RED}✗ VPC not found${NC}"
        exit 1
    fi
}

test_subnets() {
    echo -e "${YELLOW}Testing Subnets...${NC}"
    SUBNET_COUNT=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" --query 'length(Subnets)')
    if [ "$SUBNET_COUNT" -ge 6 ]; then
        echo -e "${GREEN}✓ All subnets created: $SUBNET_COUNT${NC}"
    else
        echo -e "${RED}✗ Missing subnets. Expected 6, found $SUBNET_COUNT${NC}"
        exit 1
    fi
}

test_rds() {
    echo -e "${YELLOW}Testing RDS...${NC}"
    RDS_STATUS=$(aws rds describe-db-instances --db-instance-identifier ecommerce-production-postgres --query 'DBInstances[0].DBInstanceStatus' --output text 2&amp;gt;/dev/null || echo "NOT_FOUND")
    if [ "$RDS_STATUS" = "available" ]; then
        echo -e "${GREEN}✓ RDS instance is available${NC}"
    else
        echo -e "${RED}✗ RDS instance not available. Status: $RDS_STATUS${NC}"
        exit 1
    fi
}

test_ecs() {
    echo -e "${YELLOW}Testing ECS...${NC}"
    CLUSTER_STATUS=$(aws ecs describe-clusters --clusters ecommerce-production-cluster --query 'clusters[0].status' --output text 2&amp;gt;/dev/null || echo "NOT_FOUND")
    if [ "$CLUSTER_STATUS" = "ACTIVE" ]; then
        echo -e "${GREEN}✓ ECS cluster is active${NC}"

        # Test ECS service
        SERVICE_STATUS=$(aws ecs describe-services --cluster ecommerce-production-cluster --services ecommerce-production-api-service --query 'services[0].status' --output text 2&amp;gt;/dev/null || echo "NOT_FOUND")
        if [ "$SERVICE_STATUS" = "ACTIVE" ]; then
            echo -e "${GREEN}✓ ECS service is active${NC}"
        else
            echo -e "${RED}✗ ECS service not active. Status: $SERVICE_STATUS${NC}"
        fi
    else
        echo -e "${RED}✗ ECS cluster not active. Status: $CLUSTER_STATUS${NC}"
        exit 1
    fi
}

test_alb() {
    echo -e "${YELLOW}Testing Application Load Balancer...${NC}"
    ALB_DNS=$(aws cloudformation describe-stacks --stack-name ecommerce-ecs --query 'Stacks[0].Outputs[?OutputKey==`LoadBalancerDNS`].OutputValue' --output text 2&amp;gt;/dev/null || echo "NOT_FOUND")
    if [ "$ALB_DNS" != "NOT_FOUND" ] &amp;amp;&amp;amp; [ "$ALB_DNS" != "" ]; then
        echo -e "${GREEN}✓ ALB exists: $ALB_DNS${NC}"

        # Test health endpoint
        echo -e "${YELLOW}Testing API health endpoint...${NC}"
        HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "http://$ALB_DNS/health" || echo "000")
        if [ "$HTTP_STATUS" = "200" ]; then
            echo -e "${GREEN}✓ API health check passed${NC}"
        else
            echo -e "${RED}✗ API health check failed. HTTP Status: $HTTP_STATUS${NC}"
        fi
    else
        echo -e "${RED}✗ ALB not found${NC}"
        exit 1
    fi
}

test_s3() {
    echo -e "${YELLOW}Testing S3 Buckets...${NC}"
    FRONTEND_BUCKET=$(aws cloudformation describe-stacks --stack-name ecommerce-s3 --query 'Stacks[0].Outputs[?OutputKey==`FrontendBucketName`].OutputValue' --output text 2&amp;gt;/dev/null || echo "NOT_FOUND")
    if [ "$FRONTEND_BUCKET" != "NOT_FOUND" ]; then
        echo -e "${GREEN}✓ Frontend bucket exists: $FRONTEND_BUCKET${NC}"
    else
        echo -e "${RED}✗ Frontend bucket not found${NC}"
        exit 1
    fi
}

test_cloudfront() {
    echo -e "${YELLOW}Testing CloudFront...${NC}"
    CF_DOMAIN=$(aws cloudformation describe-stacks --stack-name ecommerce-cloudfront --query 'Stacks[0].Outputs[?OutputKey==`CloudFrontDomainName`].OutputValue' --output text 2&amp;gt;/dev/null || echo "NOT_FOUND")
    if [ "$CF_DOMAIN" != "NOT_FOUND" ]; then
        echo -e "${GREEN}✓ CloudFront distribution exists: $CF_DOMAIN${NC}"

        # Test CloudFront endpoint
        echo -e "${YELLOW}Testing CloudFront endpoint...${NC}"
        CF_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://$CF_DOMAIN" || echo "000")
        if [ "$CF_STATUS" = "200" ] || [ "$CF_STATUS" = "403" ]; then
            echo -e "${GREEN}✓ CloudFront endpoint accessible${NC}"
        else
            echo -e "${RED}✗ CloudFront endpoint not accessible. HTTP Status: $CF_STATUS${NC}"
        fi
    else
        echo -e "${RED}✗ CloudFront distribution not found${NC}"
        exit 1
    fi
}

# Run all tests
echo "🚀 Starting infrastructure tests..."
test_vpc
test_subnets
test_rds
test_ecs
test_alb
test_s3
test_cloudfront

echo -e "${GREEN}🎉 All infrastructure tests passed!${NC}"

# Display useful information
echo ""
echo "📋 Infrastructure Summary:"
echo "VPC ID: $VPC_ID"
echo "ALB DNS: $ALB_DNS"
echo "CloudFront Domain: $CF_DOMAIN"
echo "API Health Check: http://$ALB_DNS/health"
echo "API Products Endpoint: http://$ALB_DNS/api/products"
echo ""
echo "🔗 Access your application:"
echo "Frontend (CloudFront): https://$CF_DOMAIN"
echo "API (Direct): http://$ALB_DNS/api/products"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2: Run Infrastructure Tests&lt;/p&gt;

&lt;p&gt;`# Make script executable&lt;br&gt;
chmod +x scripts/testing/test-infrastructure.sh&lt;/p&gt;
&lt;h1&gt;
  
  
  Run tests
&lt;/h1&gt;

&lt;p&gt;./scripts/testing/test-infrastructure.sh`&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fadyfnago8u4vql9dvmr4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fadyfnago8u4vql9dvmr4.png" alt=" " width="800" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step 3: Upload Sample Frontend Content&lt;/p&gt;

&lt;p&gt;&lt;code&gt;# Create simple frontend for testing&lt;br&gt;
mkdir -p application/frontend/dist&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Save the enhanced HTML to your file
cat &amp;gt; application/frontend/dist/index.html &amp;lt;&amp;lt; 'EOF'
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8"&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&amp;gt;
    &amp;lt;title&amp;gt;TechStore - Modern Electronics&amp;lt;/title&amp;gt;
    &amp;lt;style&amp;gt;
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            line-height: 1.6;
            color: #333;
            background: #f8f9fa;
        }

        .container {
            max-width: 1400px;
            margin: 0 auto;
            padding: 0 20px;
        }

        /* Header */
        header {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 1rem 0;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }

        .header-content {
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-wrap: wrap;
        }

        .logo {
            font-size: 2rem;
            font-weight: bold;
            text-decoration: none;
            color: white;
        }

        nav ul {
            display: flex;
            list-style: none;
            gap: 2rem;
        }

        nav a {
            color: white;
            text-decoration: none;
            font-weight: 500;
            transition: opacity 0.3s;
        }

        nav a:hover {
            opacity: 0.8;
        }

        .cart-info {
            display: flex;
            align-items: center;
            gap: 1rem;
        }

        .cart-count {
            background: #ff6b6b;
            border-radius: 50%;
            padding: 0.2rem 0.6rem;
            font-size: 0.8rem;
            font-weight: bold;
        }

        /* Hero Section */
        .hero {
            background: linear-gradient(rgba(0,0,0,0.4), rgba(0,0,0,0.4)), url('data:image/svg+xml,&amp;lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 600"&amp;gt;&amp;lt;rect fill="%23f0f0f0" width="1200" height="600"/&amp;gt;&amp;lt;text x="600" y="300" text-anchor="middle" font-size="48" fill="%23999"&amp;gt;Hero Background&amp;lt;/text&amp;gt;&amp;lt;/svg&amp;gt;');
            background-size: cover;
            background-position: center;
            color: white;
            text-align: center;
            padding: 6rem 0;
        }

        .hero h1 {
            font-size: 3.5rem;
            margin-bottom: 1rem;
            text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
        }

        .hero p {
            font-size: 1.3rem;
            margin-bottom: 2rem;
            opacity: 0.9;
        }

        .btn {
            display: inline-block;
            background: #ff6b6b;
            color: white;
            padding: 1rem 2rem;
            text-decoration: none;
            border-radius: 50px;
            font-weight: bold;
            transition: all 0.3s;
            border: none;
            cursor: pointer;
            font-size: 1rem;
        }

        .btn:hover {
            background: #ff5252;
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(255, 107, 107, 0.4);
        }

        .btn-secondary {
            background: transparent;
            border: 2px solid #ff6b6b;
            color: #ff6b6b;
        }

        .btn-secondary:hover {
            background: #ff6b6b;
            color: white;
        }

        /* API Status */
        .api-status {
            margin: 2rem 0;
            padding: 1rem;
            border-radius: 8px;
            text-align: center;
            font-weight: bold;
        }

        .status-loading {
            background: #fff3cd;
            color: #856404;
            border: 1px solid #ffeaa7;
        }

        .status-ok {
            background: #d4edda;
            color: #155724;
            border: 1px solid #c3e6cb;
        }

        .status-error {
            background: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
        }

        /* Categories */
        .categories {
            padding: 4rem 0;
            background: white;
        }

        .section-title {
            text-align: center;
            font-size: 2.5rem;
            margin-bottom: 3rem;
            color: #333;
        }

        .categories-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 2rem;
            margin-bottom: 3rem;
        }

        .category-card {
            background: linear-gradient(45deg, #667eea, #764ba2);
            color: white;
            padding: 2rem;
            border-radius: 15px;
            text-align: center;
            cursor: pointer;
            transition: transform 0.3s, box-shadow 0.3s;
        }

        .category-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 10px 25px rgba(0,0,0,0.15);
        }

        .category-icon {
            font-size: 3rem;
            margin-bottom: 1rem;
        }

        /* Products Section */
        .products-section {
            padding: 4rem 0;
            background: #f8f9fa;
        }

        .filters {
            display: flex;
            justify-content: center;
            gap: 1rem;
            margin-bottom: 3rem;
            flex-wrap: wrap;
        }

        .filter-btn {
            background: white;
            border: 2px solid #ddd;
            padding: 0.5rem 1.5rem;
            border-radius: 25px;
            cursor: pointer;
            transition: all 0.3s;
        }

        .filter-btn.active,
        .filter-btn:hover {
            background: #667eea;
            color: white;
            border-color: #667eea;
        }

        .products-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
            gap: 2rem;
        }

        .product-card {
            background: white;
            border-radius: 15px;
            overflow: hidden;
            box-shadow: 0 5px 15px rgba(0,0,0,0.08);
            transition: transform 0.3s, box-shadow 0.3s;
            cursor: pointer;
        }

        .product-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 15px 35px rgba(0,0,0,0.15);
        }

        .product-image {
            width: 100%;
            height: 250px;
            background: linear-gradient(45deg, #f0f0f0, #e0e0e0);
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 4rem;
            color: #999;
        }

        .product-info {
            padding: 1.5rem;
        }

        .product-title {
            font-size: 1.25rem;
            font-weight: bold;
            margin-bottom: 0.5rem;
            color: #333;
        }

        .product-description {
            color: #666;
            margin-bottom: 1rem;
            font-size: 0.9rem;
        }

        .product-price {
            display: flex;
            align-items: center;
            gap: 1rem;
            margin-bottom: 1rem;
        }

        .current-price {
            font-size: 1.5rem;
            font-weight: bold;
            color: #ff6b6b;
        }

        .original-price {
            text-decoration: line-through;
            color: #999;
        }

        .discount {
            background: #ff6b6b;
            color: white;
            padding: 0.2rem 0.5rem;
            border-radius: 4px;
            font-size: 0.8rem;
        }

        .product-rating {
            display: flex;
            align-items: center;
            gap: 0.5rem;
            margin-bottom: 1rem;
        }

        .stars {
            color: #ffd700;
        }

        .rating-count {
            color: #666;
            font-size: 0.9rem;
        }

        .add-to-cart {
            width: 100%;
            background: #667eea;
            color: white;
            border: none;
            padding: 0.75rem;
            border-radius: 8px;
            font-weight: bold;
            cursor: pointer;
            transition: background 0.3s;
        }

        .add-to-cart:hover {
            background: #5a6fd8;
        }

        /* Footer */
        footer {
            background: #333;
            color: white;
            padding: 3rem 0 1rem;
        }

        .footer-content {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
            gap: 2rem;
            margin-bottom: 2rem;
        }

        .footer-section h3 {
            margin-bottom: 1rem;
            color: #ff6b6b;
        }

        .footer-section ul {
            list-style: none;
        }

        .footer-section a {
            color: #ccc;
            text-decoration: none;
            transition: color 0.3s;
        }

        .footer-section a:hover {
            color: #ff6b6b;
        }

        .footer-bottom {
            text-align: center;
            padding-top: 2rem;
            border-top: 1px solid #555;
            color: #ccc;
        }

        /* Cart Modal */
        .modal {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.5);
            z-index: 1000;
        }

        .modal-content {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            padding: 2rem;
            border-radius: 15px;
            max-width: 500px;
            width: 90%;
            max-height: 80vh;
            overflow-y: auto;
        }

        .close {
            position: absolute;
            top: 1rem;
            right: 1rem;
            font-size: 2rem;
            cursor: pointer;
            color: #999;
        }

        .cart-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 1rem 0;
            border-bottom: 1px solid #eee;
        }

        .cart-total {
            font-size: 1.5rem;
            font-weight: bold;
            text-align: right;
            margin-top: 1rem;
            color: #ff6b6b;
        }

        /* Responsive */
        @media (max-width: 768px) {
            .header-content {
                flex-direction: column;
                gap: 1rem;
            }

            nav ul {
                flex-wrap: wrap;
                justify-content: center;
            }

            .hero h1 {
                font-size: 2.5rem;
            }

            .products-grid {
                grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
            }
        }

        .loading {
            text-align: center;
            padding: 2rem;
            color: #666;
        }

        .spinner {
            border: 4px solid #f3f3f3;
            border-top: 4px solid #667eea;
            border-radius: 50%;
            width: 50px;
            height: 50px;
            animation: spin 1s linear infinite;
            margin: 0 auto 1rem;
        }

        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;!-- Header --&amp;gt;
    &amp;lt;header&amp;gt;
        &amp;lt;div class="container"&amp;gt;
            &amp;lt;div class="header-content"&amp;gt;
                &amp;lt;a href="#" class="logo"&amp;gt;🛒 TechStore&amp;lt;/a&amp;gt;
                &amp;lt;nav&amp;gt;
                    &amp;lt;ul&amp;gt;
                        &amp;lt;li&amp;gt;&amp;lt;a href="#home"&amp;gt;Home&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt;&amp;lt;a href="#products"&amp;gt;Products&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt;&amp;lt;a href="#categories"&amp;gt;Categories&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt;&amp;lt;a href="#about"&amp;gt;About&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt;&amp;lt;a href="#contact"&amp;gt;Contact&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                    &amp;lt;/ul&amp;gt;
                &amp;lt;/nav&amp;gt;
                &amp;lt;div class="cart-info"&amp;gt;
                    &amp;lt;span&amp;gt;Cart: &amp;lt;span class="cart-count" id="cartCount"&amp;gt;0&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;
                    &amp;lt;button class="btn btn-secondary" onclick="openCart()"&amp;gt;View Cart&amp;lt;/button&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/header&amp;gt;

    &amp;lt;!-- Hero Section --&amp;gt;
    &amp;lt;section class="hero" id="home"&amp;gt;
        &amp;lt;div class="container"&amp;gt;
            &amp;lt;h1&amp;gt;Welcome to TechStore&amp;lt;/h1&amp;gt;
            &amp;lt;p&amp;gt;Discover the latest technology and electronics at unbeatable prices&amp;lt;/p&amp;gt;
            &amp;lt;a href="#products" class="btn"&amp;gt;Shop Now&amp;lt;/a&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;

    &amp;lt;!-- API Status --&amp;gt;
    &amp;lt;div class="container"&amp;gt;
        &amp;lt;div class="api-status status-loading" id="apiStatus"&amp;gt;
            &amp;lt;div class="spinner"&amp;gt;&amp;lt;/div&amp;gt;
            Testing API connection...
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;!-- Categories --&amp;gt;
    &amp;lt;section class="categories" id="categories"&amp;gt;
        &amp;lt;div class="container"&amp;gt;
            &amp;lt;h2 class="section-title"&amp;gt;Shop by Category&amp;lt;/h2&amp;gt;
            &amp;lt;div class="categories-grid"&amp;gt;
                &amp;lt;div class="category-card" onclick="filterProducts('smartphones')"&amp;gt;
                    &amp;lt;div class="category-icon"&amp;gt;📱&amp;lt;/div&amp;gt;
                    &amp;lt;h3&amp;gt;Smartphones&amp;lt;/h3&amp;gt;
                    &amp;lt;p&amp;gt;Latest mobile devices&amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div class="category-card" onclick="filterProducts('laptops')"&amp;gt;
                    &amp;lt;div class="category-icon"&amp;gt;💻&amp;lt;/div&amp;gt;
                    &amp;lt;h3&amp;gt;Laptops&amp;lt;/h3&amp;gt;
                    &amp;lt;p&amp;gt;Powerful computing&amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div class="category-card" onclick="filterProducts('headphones')"&amp;gt;
                    &amp;lt;div class="category-icon"&amp;gt;🎧&amp;lt;/div&amp;gt;
                    &amp;lt;h3&amp;gt;Audio&amp;lt;/h3&amp;gt;
                    &amp;lt;p&amp;gt;Premium sound quality&amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div class="category-card" onclick="filterProducts('cameras')"&amp;gt;
                    &amp;lt;div class="category-icon"&amp;gt;📷&amp;lt;/div&amp;gt;
                    &amp;lt;h3&amp;gt;Cameras&amp;lt;/h3&amp;gt;
                    &amp;lt;p&amp;gt;Capture memories&amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div class="category-card" onclick="filterProducts('gaming')"&amp;gt;
                    &amp;lt;div class="category-icon"&amp;gt;🎮&amp;lt;/div&amp;gt;
                    &amp;lt;h3&amp;gt;Gaming&amp;lt;/h3&amp;gt;
                    &amp;lt;p&amp;gt;Gaming gear&amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div class="category-card" onclick="filterProducts('accessories')"&amp;gt;
                    &amp;lt;div class="category-icon"&amp;gt;⚡&amp;lt;/div&amp;gt;
                    &amp;lt;h3&amp;gt;Accessories&amp;lt;/h3&amp;gt;
                    &amp;lt;p&amp;gt;Tech accessories&amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;

    &amp;lt;!-- Products Section --&amp;gt;
    &amp;lt;section class="products-section" id="products"&amp;gt;
        &amp;lt;div class="container"&amp;gt;
            &amp;lt;h2 class="section-title"&amp;gt;Featured Products&amp;lt;/h2&amp;gt;

            &amp;lt;div class="filters"&amp;gt;
                &amp;lt;button class="filter-btn active" onclick="filterProducts('all')"&amp;gt;All Products&amp;lt;/button&amp;gt;
                &amp;lt;button class="filter-btn" onclick="filterProducts('smartphones')"&amp;gt;Smartphones&amp;lt;/button&amp;gt;
                &amp;lt;button class="filter-btn" onclick="filterProducts('laptops')"&amp;gt;Laptops&amp;lt;/button&amp;gt;
                &amp;lt;button class="filter-btn" onclick="filterProducts('headphones')"&amp;gt;Audio&amp;lt;/button&amp;gt;
                &amp;lt;button class="filter-btn" onclick="filterProducts('cameras')"&amp;gt;Cameras&amp;lt;/button&amp;gt;
                &amp;lt;button class="filter-btn" onclick="filterProducts('gaming')"&amp;gt;Gaming&amp;lt;/button&amp;gt;
            &amp;lt;/div&amp;gt;

            &amp;lt;div class="products-grid" id="productsGrid"&amp;gt;
                &amp;lt;div class="loading"&amp;gt;
                    &amp;lt;div class="spinner"&amp;gt;&amp;lt;/div&amp;gt;
                    Loading products...
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;

    &amp;lt;!-- Footer --&amp;gt;
    &amp;lt;footer&amp;gt;
        &amp;lt;div class="container"&amp;gt;
            &amp;lt;div class="footer-content"&amp;gt;
                &amp;lt;div class="footer-section"&amp;gt;
                    &amp;lt;h3&amp;gt;About TechStore&amp;lt;/h3&amp;gt;
                    &amp;lt;p&amp;gt;Your trusted partner for the latest technology and electronics. We offer high-quality products at competitive prices with excellent customer service.&amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div class="footer-section"&amp;gt;
                    &amp;lt;h3&amp;gt;Quick Links&amp;lt;/h3&amp;gt;
                    &amp;lt;ul&amp;gt;
                        &amp;lt;li&amp;gt;&amp;lt;a href="#home"&amp;gt;Home&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt;&amp;lt;a href="#products"&amp;gt;Products&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt;&amp;lt;a href="#categories"&amp;gt;Categories&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt;&amp;lt;a href="#about"&amp;gt;About Us&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                    &amp;lt;/ul&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div class="footer-section"&amp;gt;
                    &amp;lt;h3&amp;gt;Customer Service&amp;lt;/h3&amp;gt;
                    &amp;lt;ul&amp;gt;
                        &amp;lt;li&amp;gt;&amp;lt;a href="#"&amp;gt;Contact Us&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt;&amp;lt;a href="#"&amp;gt;Shipping Info&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt;&amp;lt;a href="#"&amp;gt;Returns&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt;&amp;lt;a href="#"&amp;gt;FAQ&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                    &amp;lt;/ul&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div class="footer-section"&amp;gt;
                    &amp;lt;h3&amp;gt;Connect&amp;lt;/h3&amp;gt;
                    &amp;lt;ul&amp;gt;
                        &amp;lt;li&amp;gt;&amp;lt;a href="#"&amp;gt;Facebook&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt;&amp;lt;a href="#"&amp;gt;Twitter&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt;&amp;lt;a href="#"&amp;gt;Instagram&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt;&amp;lt;a href="#"&amp;gt;LinkedIn&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                    &amp;lt;/ul&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="footer-bottom"&amp;gt;
                &amp;lt;p&amp;gt;&amp;amp;copy; 2025 TechStore. All rights reserved. | Built with AWS&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/footer&amp;gt;

    &amp;lt;!-- Cart Modal --&amp;gt;
    &amp;lt;div class="modal" id="cartModal"&amp;gt;
        &amp;lt;div class="modal-content"&amp;gt;
            &amp;lt;span class="close" onclick="closeCart()"&amp;gt;&amp;amp;times;&amp;lt;/span&amp;gt;
            &amp;lt;h2&amp;gt;Shopping Cart&amp;lt;/h2&amp;gt;
            &amp;lt;div id="cartItems"&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;div class="cart-total" id="cartTotal"&amp;gt;Total: $0.00&amp;lt;/div&amp;gt;
            &amp;lt;button class="btn" style="width: 100%; margin-top: 1rem;" onclick="checkout()"&amp;gt;Proceed to Checkout&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;script&amp;gt;
        // Sample product data
        const products = [
            {
                id: 1,
                name: "iPhone 15 Pro",
                category: "smartphones",
                price: 999,
                originalPrice: 1099,
                description: "Latest iPhone with titanium design and A17 Pro chip",
                rating: 4.8,
                reviews: 1234,
                image: "📱"
            },
            {
                id: 2,
                name: "MacBook Pro M3",
                category: "laptops",
                price: 1999,
                originalPrice: 2199,
                description: "Powerful laptop with M3 chip for professionals",
                rating: 4.9,
                reviews: 856,
                image: "💻"
            },
            {
                id: 3,
                name: "Sony WH-1000XM5",
                category: "headphones",
                price: 399,
                originalPrice: 449,
                description: "Industry-leading noise canceling headphones",
                rating: 4.7,
                reviews: 2103,
                image: "🎧"
            },
            {
                id: 4,
                name: "Canon EOS R5",
                category: "cameras",
                price: 3899,
                originalPrice: 4299,
                description: "Professional mirrorless camera with 45MP sensor",
                rating: 4.6,
                reviews: 445,
                image: "📷"
            },
            {
                id: 5,
                name: "PlayStation 5",
                category: "gaming",
                price: 499,
                originalPrice: 559,
                description: "Next-gen gaming console with ray tracing",
                rating: 4.8,
                reviews: 3421,
                image: "🎮"
            },
            {
                id: 6,
                name: "iPad Pro 12.9\"",
                category: "smartphones",
                price: 1099,
                originalPrice: 1199,
                description: "Professional tablet with M2 chip",
                rating: 4.7,
                reviews: 1876,
                image: "📱"
            },
            {
                id: 7,
                name: "Dell XPS 13",
                category: "laptops",
                price: 1299,
                originalPrice: 1399,
                description: "Ultrabook with InfinityEdge display",
                rating: 4.5,
                reviews: 934,
                image: "💻"
            },
            {
                id: 8,
                name: "AirPods Pro",
                category: "headphones",
                price: 249,
                originalPrice: 279,
                description: "Wireless earbuds with active noise cancellation",
                rating: 4.6,
                reviews: 5672,
                image: "🎧"
            }
        ];

        let cart = [];
        let currentFilter = 'all';

        // Initialize the page
        document.addEventListener('DOMContentLoaded', function() {
            testAPIConnection();
            displayProducts();
        });

        // Test API connection
        function testAPIConnection() {
            const apiStatus = document.getElementById('apiStatus');

            // Simulate API test
            setTimeout(() =&amp;gt; {
                const isConnected = Math.random() &amp;gt; 0.3; // 70% success rate for demo

                if (isConnected) {
                    apiStatus.className = 'api-status status-ok';
                    apiStatus.innerHTML = '✅ API Connection Successful - Backend services are running';
                } else {
                    apiStatus.className = 'api-status status-error';
                    apiStatus.innerHTML = '❌ API Connection Failed - Using demo data';
                }
            }, 2000);
        }

        // Display products
        function displayProducts() {
            const grid = document.getElementById('productsGrid');
            const filteredProducts = currentFilter === 'all' ? products : products.filter(p =&amp;gt; p.category === currentFilter);

            setTimeout(() =&amp;gt; {
                grid.innerHTML = filteredProducts.map(product =&amp;gt; `
                    &amp;lt;div class="product-card"&amp;gt;
                        &amp;lt;div class="product-image"&amp;gt;${product.image}&amp;lt;/div&amp;gt;
                        &amp;lt;div class="product-info"&amp;gt;
                            &amp;lt;h3 class="product-title"&amp;gt;${product.name}&amp;lt;/h3&amp;gt;
                            &amp;lt;p class="product-description"&amp;gt;${product.description}&amp;lt;/p&amp;gt;
                            &amp;lt;div class="product-rating"&amp;gt;
                                &amp;lt;span class="stars"&amp;gt;${'★'.repeat(Math.floor(product.rating))}${product.rating % 1 ? '☆' : ''}&amp;lt;/span&amp;gt;
                                &amp;lt;span class="rating-count"&amp;gt;${product.rating} (${product.reviews} reviews)&amp;lt;/span&amp;gt;
                            &amp;lt;/div&amp;gt;
                            &amp;lt;div class="product-price"&amp;gt;
                                &amp;lt;span class="current-price"&amp;gt;$${product.price}&amp;lt;/span&amp;gt;
                                ${product.originalPrice &amp;gt; product.price ? `
                                    &amp;lt;span class="original-price"&amp;gt;$${product.originalPrice}&amp;lt;/span&amp;gt;
                                    &amp;lt;span class="discount"&amp;gt;${Math.round((1 - product.price/product.originalPrice) * 100)}% OFF&amp;lt;/span&amp;gt;
                                ` : ''}
                            &amp;lt;/div&amp;gt;
                            &amp;lt;button class="add-to-cart" onclick="addToCart(${product.id})"&amp;gt;Add to Cart&amp;lt;/button&amp;gt;
                        &amp;lt;/div&amp;gt;
                    &amp;lt;/div&amp;gt;
                `).join('');
            }, 500);
        }

        // Filter products
        function filterProducts(category) {
            currentFilter = category;

            // Update filter buttons
            document.querySelectorAll('.filter-btn').forEach(btn =&amp;gt; btn.classList.remove('active'));
            event.target.classList.add('active');

            // Show loading
            document.getElementById('productsGrid').innerHTML = `
                &amp;lt;div class="loading"&amp;gt;
                    &amp;lt;div class="spinner"&amp;gt;&amp;lt;/div&amp;gt;
                    Loading ${category === 'all' ? 'all products' : category}...
                &amp;lt;/div&amp;gt;
            `;

            displayProducts();
        }

        // Add to cart
        function addToCart(productId) {
            const product = products.find(p =&amp;gt; p.id === productId);
            const existingItem = cart.find(item =&amp;gt; item.id === productId);

            if (existingItem) {
                existingItem.quantity += 1;
            } else {
                cart.push({...product, quantity: 1});
            }

            updateCartCount();

            // Show feedback
            event.target.style.background = '#28a745';
            event.target.textContent = 'Added!';
            setTimeout(() =&amp;gt; {
                event.target.style.background = '#667eea';
                event.target.textContent = 'Add to Cart';
            }, 1000);
        }

        // Update cart count
        function updateCartCount() {
            const count = cart.reduce((total, item) =&amp;gt; total + item.quantity, 0);
            document.getElementById('cartCount').textContent = count;
        }

        // Open cart modal
        function openCart() {
            const modal = document.getElementById('cartModal');
            const cartItems = document.getElementById('cartItems');
            const cartTotal = document.getElementById('cartTotal');

            if (cart.length === 0) {
                cartItems.innerHTML = '&amp;lt;p style="text-align: center; color: #666; padding: 2rem;"&amp;gt;Your cart is empty&amp;lt;/p&amp;gt;';
                cartTotal.textContent = 'Total: $0.00';
            } else {
                cartItems.innerHTML = cart.map(item =&amp;gt; `
                    &amp;lt;div class="cart-item"&amp;gt;
                        &amp;lt;div&amp;gt;
                            &amp;lt;strong&amp;gt;${item.name}&amp;lt;/strong&amp;gt;&amp;lt;br&amp;gt;
                            &amp;lt;small&amp;gt;${item.description}&amp;lt;/small&amp;gt;&amp;lt;br&amp;gt;
                            &amp;lt;small&amp;gt;Quantity: ${item.quantity}&amp;lt;/small&amp;gt;
                        &amp;lt;/div&amp;gt;
                        &amp;lt;div&amp;gt;
                            &amp;lt;strong&amp;gt;$${(item.price * item.quantity).toFixed(2)}&amp;lt;/strong&amp;gt;
                            &amp;lt;button onclick="removeFromCart(${item.id})" style="margin-left: 1rem; background: #ff6b6b; color: white; border: none; padding: 0.2rem 0.5rem; border-radius: 4px; cursor: pointer;"&amp;gt;Remove&amp;lt;/button&amp;gt;
                        &amp;lt;/div&amp;gt;
                    &amp;lt;/div&amp;gt;
                `).join('');

                const total = cart.reduce((sum, item) =&amp;gt; sum + (item.price * item.quantity), 0);
                cartTotal.textContent = `Total: $${total.toFixed(2)}`;
            }

            modal.style.display = 'block';
        }

        // Close cart modal
        function closeCart() {
            document.getElementById('cartModal').style.display = 'none';
        }

        // Remove from cart
        function removeFromCart(productId) {
            cart = cart.filter(item =&amp;gt; item.id !== productId);
            updateCartCount();
            openCart(); // Refresh cart display
        }

        // Checkout
        function checkout() {
            if (cart.length === 0) {
                alert('Your cart is empty!');
                return;
            }

            const total = cart.reduce((sum, item) =&amp;gt; sum + (item.price * item.quantity), 0);
            alert(`Thank you for your purchase! Total: $${total.toFixed(2)}\n\nThis is a demo - no actual payment processed.`);

            cart = [];
            updateCartCount();
            closeCart();
        }

        // Close modal when clicking outside
        window.onclick = function(event) {
            const modal = document.getElementById('cartModal');
            if (event.target === modal) {
                closeCart();
            }
        }

        // Smooth scrolling for navigation links
        document.querySelectorAll('a[href^="#"]').forEach(anchor =&amp;gt; {
            anchor.addEventListener('click', function (e) {
                e.preventDefault();
                const target = document.querySelector(this.getAttribute('href'));
                if (target) {
                    target.scrollIntoView({
                        behavior: 'smooth',
                        block: 'start'
                    });
                }
            });
        });
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
EOF

# Upload to S3
FRONTEND_BUCKET=$(aws cloudformation describe-stacks --stack-name ecommerce-cloudfront --query 'Stacks[0].Outputs[?OutputKey==`FrontendBucketName`].OutputValue' --output text)
aws s3 cp application/frontend/dist/index.html s3://$FRONTEND_BUCKET/index.html --content-type text/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Upload to S3
FRONTEND_BUCKET=$(aws cloudformation describe-stacks --stack-name ecommerce-s3 --query 'Stacks[0].Outputs[?OutputKey==`FrontendBucketName`].OutputValue' --output text)

If you are in dist directory use this command
aws s3 cp index.html s3://$FRONTEND_BUCKET/index.html --content-type text/html

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjbjbc5b9umf69vswvhh0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjbjbc5b9umf69vswvhh0.png" alt=" " width="800" height="123"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CI/CD Pipeline and Advanced Features&lt;br&gt;
CodePipeline Setup&lt;br&gt;
Step 1: Create CodeBuild Template&lt;/p&gt;

&lt;p&gt;touch cloudformation/cicd/codebuild-project.yaml&lt;/p&gt;

&lt;p&gt;File: cloudformation/cicd/codebuild-project.yaml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWSTemplateFormatVersion: '2010-09-09'
Description: 'CodePipeline for E-commerce API deployment'

Parameters:
  ProjectName:
    Type: String
    Default: ecommerce
  Environment:
    Type: String
    Default: production
  GitHubOwner:
    Type: String
    Description: GitHub repository owner
  GitHubRepo:
    Type: String
    Description: GitHub repository name
    Default: ecommerce-infrastructure
  GitHubBranch:
    Type: String
    Description: GitHub branch
    Default: main
  GitHubToken:
    Type: String
    Description: GitHub personal access token
    NoEcho: true

Resources:
  # CodePipeline Service Role
  CodePipelineServiceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub '${ProjectName}-${Environment}-codepipeline-role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: CodePipelinePolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetBucketVersioning
                  - s3:GetObject
                  - s3:GetObjectVersion
                  - s3:PutObject
                  - s3:GetBucketLocation
                  - s3:ListBucket
                Resource:
                  - Fn::ImportValue: !Sub '${ProjectName}-${Environment}-Artifacts-Bucket'
                  - !Sub 
                    - '${BucketArn}/*'
                    - BucketArn:
                        Fn::ImportValue: !Sub '${ProjectName}-${Environment}-Artifacts-Bucket'
              - Effect: Allow
                Action:
                  - codebuild:BatchGetBuilds
                  - codebuild:StartBuild
                Resource:
                  - Fn::ImportValue: !Sub '${ProjectName}-${Environment}-CodeBuild-Project'
              - Effect: Allow
                Action:
                  - ecs:DescribeServices
                  - ecs:DescribeTaskDefinition
                  - ecs:DescribeTasks
                  - ecs:ListTasks
                  - ecs:RegisterTaskDefinition
                  - ecs:UpdateService
                Resource: '*'
              - Effect: Allow
                Action:
                  - iam:PassRole
                Resource: '*'

  # CodeDeploy Application
  CodeDeployApplication:
    Type: AWS::CodeDeploy::Application
    Properties:
      ApplicationName: !Sub '${ProjectName}-${Environment}-ecs-app'
      ComputePlatform: ECS

  # CodeDeploy Service Role
  CodeDeployServiceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub '${ProjectName}-${Environment}-codedeploy-role'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: codedeploy.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AWSCodeDeployRoleForECS

  # CodeDeploy Deployment Group
  CodeDeployDeploymentGroup:
    Type: AWS::CodeDeploy::DeploymentGroup
    Properties:
      ApplicationName: !Ref CodeDeployApplication
      DeploymentGroupName: !Sub '${ProjectName}-${Environment}-deployment-group'
      ServiceRoleArn: !GetAtt CodeDeployServiceRole.Arn
      DeploymentConfigName: CodeDeployDefault.ECSAllAtOnce
      ECSServices:
        - ServiceName: 
            Fn::ImportValue: !Sub '${ProjectName}-${Environment}-ECS-Service'
          ClusterName:
            Fn::ImportValue: !Sub '${ProjectName}-${Environment}-ECS-Cluster'
      LoadBalancerInfo:
        TargetGroupInfoList:
          - Name: !Sub '${ProjectName}-${Environment}-tg'
      BlueGreenDeploymentConfiguration:
        TerminateBlueInstancesOnDeploymentSuccess:
          Action: TERMINATE
          TerminationWaitTimeInMinutes: 5
        DeploymentReadyOption:
          ActionOnTimeout: CONTINUE_DEPLOYMENT
        GreenFleetProvisioningOption:
          Action: COPY_AUTO_SCALING_GROUP

  # CodePipeline
  CodePipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      Name: !Sub '${ProjectName}-${Environment}-pipeline'
      RoleArn: !GetAtt CodePipelineServiceRole.Arn
      ArtifactStore:
        Type: S3
        Location:
          Fn::ImportValue: !Sub '${ProjectName}-${Environment}-Artifacts-Bucket'
      Stages:
        - Name: Source
          Actions:
            - Name: SourceAction
              ActionTypeId:
                Category: Source
                Owner: ThirdParty
                Provider: GitHub
                Version: 1
              Configuration:
                Owner: !Ref GitHubOwner
                Repo: !Ref GitHubRepo
                Branch: !Ref GitHubBranch
                OAuthToken: !Ref GitHubToken
                PollForSourceChanges: false
              OutputArtifacts:
                - Name: SourceOutput

        - Name: Build
          Actions:
            - Name: BuildAction
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: 1
              Configuration:
                ProjectName:
                  Fn::ImportValue: !Sub '${ProjectName}-${Environment}-CodeBuild-Project'
              InputArtifacts:
                - Name: SourceOutput
              OutputArtifacts:
                - Name: BuildOutput

        - Name: Deploy
          Actions:
            - Name: DeployAction
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: ECS
                Version: 1
              Configuration:
                ClusterName:
                  Fn::ImportValue: !Sub '${ProjectName}-${Environment}-ECS-Cluster'
                ServiceName:
                  Fn::ImportValue: !Sub '${ProjectName}-${Environment}-ECS-Service'
                FileName: imagedefinitions.json
              InputArtifacts:
                - Name: BuildOutput

  # GitHub Webhook
  GitHubWebhook:
    Type: AWS::CodePipeline::Webhook
    Properties:
      Name: !Sub '${ProjectName}-${Environment}-github-webhook'
      TargetPipeline: !Ref CodePipeline
      TargetAction: SourceAction
      TargetPipelineVersion: !GetAtt CodePipeline.Version
      RegisterWithThirdParty: true
      Authentication: GITHUB_HMAC
      AuthenticationConfiguration:
        SecretToken: !Ref GitHubToken
      Filters:
        - JsonPath: "$.ref"
          MatchEquals: !Sub "refs/heads/${GitHubBranch}"

Outputs:
  CodePipelineName:
    Description: CodePipeline name
    Value: !Ref CodePipeline
    Export:
      Name: !Sub '${ProjectName}-${Environment}-Pipeline'

  CodeDeployApplication:
    Description: CodeDeploy application name
    Value: !Ref CodeDeployApplication
    Export:
      Name: !Sub '${ProjectName}-${Environment}-CodeDeploy-App'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub Repository Setup&lt;br&gt;
Step 1: Create GitHub Repository&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
bash# 
Create a new directory for your GitHub repo
mkdir ../ecommerce-infrastructure-repo
cd ../ecommerce-infrastructure-repo

# Initialize Git repository
git init
git branch -M main

# Copy your project files
cp -r ../aws-ecommerce-infrastructure/* .

# Create README
cat &amp;gt; README.md &amp;lt;&amp;lt; 'EOF'
# E-commerce Infrastructure on AWS

This repository contains the Infrastructure as Code (IaC) for a production-ready e-commerce platform built on AWS.

## Architecture Overview

- **Frontend**: React SPA served via CloudFront + S3
- **Backend**: Node.js API running on ECS Fargate
- **Database**: RDS PostgreSQL with encryption
- **Storage**: S3 buckets with lifecycle policies
- **CDN**: CloudFront for global content delivery
- **CI/CD**: CodePipeline + CodeDeploy for automated deployments

## Deployment Instructions

1. Deploy VPC: `aws cloudformation create-stack --stack-name ecommerce-vpc --template-body file://cloudformation/network/vpc-base.yaml`
2. Deploy Security Groups: `aws cloudformation create-stack --stack-name ecommerce-security-groups --template-body file://cloudformation/security/security-groups.yaml`
3. Deploy IAM Roles: `aws cloudformation create-stack --stack-name ecommerce-iam-roles --template-body file://cloudformation/security/iam-roles.yaml --capabilities CAPABILITY_NAMED_IAM`

## Security Features

- VPC with private subnets
- Security groups with least privilege
- IAM roles with minimal permissions
- Encrypted storage (S3, RDS)
- WAF protection
- CloudWatch monitoring

## Cost Optimization

- Fargate for serverless containers
- S3 lifecycle policies
- CloudFront edge caching
- RDS right-sizing

EOF

# Create .gitignore
cat &amp;gt; .gitignore &amp;lt;&amp;lt; 'EOF'
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# IDE files
.vscode/
.idea/
*.swp
*.swo

# Node.js
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# AWS
.aws/

# Logs
logs
*.log

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Build outputs
dist/
build/
EOF

# Add all files and commit
git add .
git commit -m "Initial commit: AWS E-commerce Infrastructure"

# Create GitHub repository (you'll need GitHub CLI or do this manually)
# gh repo create ecommerce-infrastructure --public --source=. --
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2: Create GitHub Personal Access Token&lt;/p&gt;

&lt;p&gt;Go to GitHub → Settings → Developer settings → Personal access tokens&lt;br&gt;
Click "Generate new token (classic)"&lt;br&gt;
Select scopes: repo, admin:repo_hook&lt;br&gt;
Copy the token for later use&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Event Announcement System (SNS + Lambda + API Gateway)</title>
      <dc:creator>Isaac Obuor</dc:creator>
      <pubDate>Sat, 14 Jun 2025 19:14:49 +0000</pubDate>
      <link>https://dev.to/isaac_obuor_4ec2278316110/event-announcement-system-sns-lambda-api-gateway-59i1</link>
      <guid>https://dev.to/isaac_obuor_4ec2278316110/event-announcement-system-sns-lambda-api-gateway-59i1</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftrb52zre47sxnlhbv64i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftrb52zre47sxnlhbv64i.png" alt="Image description" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AWS Event Notification System - Step-by-Step Guide&lt;br&gt;
🏗️ Architecture Overview&lt;br&gt;
Frontend (S3) → API Gateway → Lambda Functions → SNS → Email Notifications&lt;br&gt;
Components:&lt;/p&gt;

&lt;p&gt;S3 Static Website: Frontend hosting&lt;br&gt;
API Gateway: REST API endpoints&lt;br&gt;
Lambda Functions: Backend logic (2 functions)&lt;br&gt;
SNS: Email notification service&lt;br&gt;
IAM Roles: Permissions management&lt;/p&gt;

&lt;p&gt;📋 Prerequisites&lt;/p&gt;

&lt;p&gt;AWS Account with appropriate permissions&lt;br&gt;
Basic knowledge of HTML/CSS/JavaScript&lt;br&gt;
AWS CLI installed (optional but recommended)&lt;/p&gt;

&lt;p&gt;**&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Create SNS Topic
&lt;/h2&gt;

&lt;p&gt;1.1 Create SNS Topic&lt;br&gt;
** Via AWS CLI&lt;br&gt;
aws sns create-topic --name EventNotifications&lt;/p&gt;
&lt;h1&gt;
  
  
  Or use AWS Console:
&lt;/h1&gt;
&lt;h1&gt;
  
  
  Services → SNS → Topics → Create topic
&lt;/h1&gt;
&lt;h1&gt;
  
  
  Name: EventNotifications
&lt;/h1&gt;
&lt;h1&gt;
  
  
  Type: Standard
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsdmqq3o4qoyp2wmgk6ec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsdmqq3o4qoyp2wmgk6ec.png" alt="Image description" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click on 'Next'&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Select' Standard' and click 'Create Topic'&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F76pz7uy41qd73foblrml.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F76pz7uy41qd73foblrml.png" alt="Image description" width="800" height="306"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;1.2 Note the Topic ARN&lt;br&gt;
Save the ARN - you'll need it later:&lt;br&gt;
arn:aws:sns:us-east-1:944355517192:EventNotifications&lt;/p&gt;

&lt;p&gt;Step 2: Create IAM Roles&lt;br&gt;
2.1 Lambda Execution Role&lt;br&gt;
Create a role with these policies:&lt;/p&gt;

&lt;p&gt;AWSLambdaBasicExecutionRole&lt;br&gt;
Custom policy for SNS access:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd79i7bubz3jku5ym9cre.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd79i7bubz3jku5ym9cre.png" alt="Image description" width="800" height="295"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on 'Create policy'&lt;/p&gt;

&lt;p&gt;Custom SNS Policy:&lt;br&gt;
Copy your SNS ARN and paste it inside your IAM role policy JSON.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sns:Subscribe",
                "sns:Publish",
                "sns:ListSubscriptionsByTopic"
            ],
            "Resource": "arn:aws:sns:*:*:EventNotifications"
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fymoum6umnbkdsey6j3uf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fymoum6umnbkdsey6j3uf.png" alt="Image description" width="800" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step 3: Create Lambda Functions&lt;br&gt;
Type 'Lambda' at the AWS management console search, and click on 'Create a function'&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20og3fqnxejh153c7l6r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20og3fqnxejh153c7l6r.png" alt="Image description" width="800" height="346"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmtpbl5qn5oh37rcyy0iw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmtpbl5qn5oh37rcyy0iw.png" alt="Image description" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3.1 Subscription Lambda Function&lt;/p&gt;

&lt;p&gt;Select 'Author from scratch' and name your Lambda function&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu425xcqllet2qdot6czj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu425xcqllet2qdot6czj.png" alt="Image description" width="800" height="294"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Function Name: event-subscription-handler&lt;br&gt;
Runtime: Python 3.9&lt;br&gt;
Architecture: arm64&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxoe1w6ncs1t7o7ifdtvu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxoe1w6ncs1t7o7ifdtvu.png" alt="Image description" width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click 'additional configurations' and check the following: AWS_IAM, Buffered, and VPC. &lt;br&gt;
Now select your respective VPC, Subnet, and Security groups &lt;br&gt;
Click on 'Create function'&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0550ygj6ikzkvkujb9b7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0550ygj6ikzkvkujb9b7.png" alt="Image description" width="800" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvdwwetj9uugrdimin6k6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvdwwetj9uugrdimin6k6.png" alt="Image description" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select 'Code', copy the Python code and paste it inside lambda_function.py&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import json
import boto3
import os
from botocore.exceptions import ClientError

def lambda_handler(event, context):
    # Initialize SNS client
    sns = boto3.client('sns')

    # Get SNS topic ARN from environment variable
    topic_arn = os.environ['SNS_TOPIC_ARN']

    try:
        # Parse request body
        if event.get('body'):
            body = json.loads(event['body'])
        else:
            body = event

        email = body.get('email')

        if not email:
            return {
                'statusCode': 400,
                'headers': {
                    'Access-Control-Allow-Origin': '*',
                    'Access-Control-Allow-Headers': 'Content-Type',
                    'Access-Control-Allow-Methods': 'POST, OPTIONS'
                },
                'body': json.dumps({
                    'error': 'Email address is required'
                })
            }

        # Subscribe email to SNS topic
        response = sns.subscribe(
            TopicArn=topic_arn,
            Protocol='email',
            Endpoint=email
        )

        return {
            'statusCode': 200,
            'headers': {
                'Access-Control-Allow-Origin': '*',
                'Access-Control-Allow-Headers': 'Content-Type',
                'Access-Control-Allow-Methods': 'POST, OPTIONS'
            },
            'body': json.dumps({
                'message': 'Subscription successful! Please check your email to confirm.',
                'subscriptionArn': response['SubscriptionArn']
            })
        }

    except ClientError as e:
        print(f"AWS Error: {e}")
        return {
            'statusCode': 500,
            'headers': {
                'Access-Control-Allow-Origin': '*',
                'Access-Control-Allow-Headers': 'Content-Type',
                'Access-Control-Allow-Methods': 'POST, OPTIONS'
            },
            'body': json.dumps({
                'error': 'Failed to subscribe to notifications'
            })
        }
    except Exception as e:
        print(f"Error: {e}")
        return {
            'statusCode': 500,
            'headers': {
                'Access-Control-Allow-Origin': '*',
                'Access-Control-Allow-Headers': 'Content-Type',
                'Access-Control-Allow-Methods': 'POST, OPTIONS'
            },
            'body': json.dumps({
                'error': 'Internal server error'
            })
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code source, click on 'Environment Variables and enter your  value:&lt;br&gt;
Key: SNS_TOPIC_ARN&lt;br&gt;
Value: arn:aws:sns:us-east-1:944355517192:EventNotifications&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5j75bkqlaq0m8kagkcju.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5j75bkqlaq0m8kagkcju.png" alt="Image description" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on 'Save'&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr04k8jg0ic1m1q96tqln.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr04k8jg0ic1m1q96tqln.png" alt="Image description" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on 'Deploy'&lt;/p&gt;

&lt;p&gt;3.2 Create another event Lambda function&lt;br&gt;
Function Name: event-creation-handler&lt;br&gt;
Runtime: Python 3.9&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftu7j4fj1gzy25a27aye5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftu7j4fj1gzy25a27aye5.png" alt="Image description" width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on 'create function'&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8llauuh3tk4mmlzxmmw3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8llauuh3tk4mmlzxmmw3.png" alt="Image description" width="800" height="325"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Select 'Code', copy the Python code and paste it inside lambda_function.py


import json
import boto3
import os
from datetime import datetime
from botocore.exceptions import ClientError

def lambda_handler(event, context):
    # Initialize SNS client
    sns = boto3.client('sns')

    # Get SNS topic ARN from environment variable
    topic_arn = os.environ['SNS_TOPIC_ARN']

    try:
        # Parse request body
        if event.get('body'):
            body = json.loads(event['body'])
        else:
            body = event

        event_name = body.get('eventName')
        event_description = body.get('eventDescription')
        event_date = body.get('eventDate')

        if not all([event_name, event_description, event_date]):
            return {
                'statusCode': 400,
                'headers': {
                    'Access-Control-Allow-Origin': '*',
                    'Access-Control-Allow-Headers': 'Content-Type',
                    'Access-Control-Allow-Methods': 'POST, OPTIONS'
                },
                'body': json.dumps({
                    'error': 'All event fields are required (eventName, eventDescription, eventDate)'
                })
            }

        # Create notification message
        message = f"""
🎉 New Event Alert! 🎉

Event: {event_name}
Description: {event_description}
Date: {event_date}

Don't miss out on this exciting event!

---
This is an automated notification from the Event System.
        """.strip()

        # Create subject
        subject = f"New Event: {event_name}"

        # Publish to SNS topic
        response = sns.publish(
            TopicArn=topic_arn,
            Message=message,
            Subject=subject
        )

        return {
            'statusCode': 200,
            'headers': {
                'Access-Control-Allow-Origin': '*',
                'Access-Control-Allow-Headers': 'Content-Type',
                'Access-Control-Allow-Methods': 'POST, OPTIONS'
            },
            'body': json.dumps({
                'message': 'Event created and notifications sent successfully!',
                'messageId': response['MessageId'],
                'event': {
                    'name': event_name,
                    'description': event_description,
                    'date': event_date,
                    'created_at': datetime.now().isoformat()
                }
            })
        }

    except ClientError as e:
        print(f"AWS Error: {e}")
        return {
            'statusCode': 500,
            'headers': {
                'Access-Control-Allow-Origin': '*',
                'Access-Control-Allow-Headers': 'Content-Type',
                'Access-Control-Allow-Methods': 'POST, OPTIONS'
            },
            'body': json.dumps({
                'error': 'Failed to create event or send notifications'
            })
        }
    except Exception as e:
        print(f"Error: {e}")
        return {
            'statusCode': 500,
            'headers': {
                'Access-Control-Allow-Origin': '*',
                'Access-Control-Allow-Headers': 'Content-Type',
                'Access-Control-Allow-Methods': 'POST, OPTIONS'
            },
            'body': json.dumps({
                'error': 'Internal server error'
            })
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3.3 Environment Variables&lt;br&gt;
For both Lambda functions, add environment variable:&lt;/p&gt;

&lt;p&gt;In the code source, click on 'Environment Variables and enter your  value:&lt;br&gt;
Key: SNS_TOPIC_ARN&lt;br&gt;
Value: arn:aws:sns:us-east-1:944355517192:EventNotifications&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8q0oj034gukvt6cfnqah.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8q0oj034gukvt6cfnqah.png" alt="Image description" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on 'Save'&lt;/p&gt;

&lt;p&gt;The SNS_TOPIC_ARN is an environment variable that you need to set in your Lambda function. It's not something you "find" - you configure it!&lt;/p&gt;

&lt;p&gt;You need to set these environment variables for each Lambda function:&lt;br&gt;
For Subscription Handler Lambda:&lt;/p&gt;

&lt;p&gt;Key: SNS_TOPIC_ARN&lt;br&gt;
Value: arn:aws:sns:us-east-1:123456789012:EventNotifications&lt;/p&gt;

&lt;p&gt;For Event Creation Handler Lambda:&lt;/p&gt;

&lt;p&gt;Key: SNS_TOPIC_ARN&lt;br&gt;
Value: arn:aws:sns:us-east-1:123456789012:EventNotifications&lt;/p&gt;

&lt;p&gt;Now you have to test both events on your 'Test event'&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw63r4qardfa7tn3dmpat.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw63r4qardfa7tn3dmpat.png" alt="Image description" width="800" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter your event name: 'event-creation-handler'&lt;br&gt;
Your template should be: 'SNS Topic Notification'&lt;/p&gt;

&lt;p&gt;Step 4: Create API Gateway&lt;br&gt;
4.1 Create REST API&lt;/p&gt;

&lt;p&gt;Go to API Gateway console&lt;br&gt;
Create API → REST API → Build&lt;br&gt;
API Name: EventNotificationAPI&lt;br&gt;
Description: API for event notification system&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuj9yg3i2mo5t9cmdsvci.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuj9yg3i2mo5t9cmdsvci.png" alt="Image description" width="800" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on 'Create'&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6utidc94lmi8o4refqdt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6utidc94lmi8o4refqdt.png" alt="Image description" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on 'Create API'&lt;/p&gt;

&lt;p&gt;4.2 Create Resources and Methods&lt;br&gt;
Resource 1: /subscribe&lt;/p&gt;

&lt;p&gt;Actions → Create Resource&lt;br&gt;
Resource Name: subscribe&lt;br&gt;
Actions → Create Method → POST&lt;br&gt;
Integration Type: Lambda Function&lt;br&gt;
Lambda Function: event-subscription-handler&lt;br&gt;
Enable CORS&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62lmxeb9cg86krbjl9ax.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62lmxeb9cg86krbjl9ax.png" alt="Image description" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on 'create resource'&lt;br&gt;
Enter your resource name: 'subscribe' &lt;br&gt;
Click on 'Create resource'&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbuzyakjst33qy7c72e43.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbuzyakjst33qy7c72e43.png" alt="Image description" width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg9615op1utqw4jjrtwob.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg9615op1utqw4jjrtwob.png" alt="Image description" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fofgakzhp3m8cop5seygo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fofgakzhp3m8cop5seygo.png" alt="Image description" width="800" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on 'Create method'&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwyuv1iqz1vz7bfn9ffq8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwyuv1iqz1vz7bfn9ffq8.png" alt="Image description" width="800" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe2p5btfi3pesq0qey12f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe2p5btfi3pesq0qey12f.png" alt="Image description" width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check 'Default 5XX and Post' and click on 'Save' &lt;/p&gt;

&lt;p&gt;You have to create the same API for your 'event-creation-handler'&lt;/p&gt;

&lt;p&gt;Resource 2: /create-event&lt;/p&gt;

&lt;p&gt;Actions → Create Resource&lt;br&gt;
Resource Name: create-event&lt;br&gt;
Actions → Create Method → POST&lt;br&gt;
Integration Type: Lambda Function&lt;br&gt;
Lambda Function: event-creation-handler&lt;br&gt;
Enable CORS&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsib2vcqx024piplorw4m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsib2vcqx024piplorw4m.png" alt="Image description" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on 'Create resource'&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr87elbrvacmsh83iu70z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr87elbrvacmsh83iu70z.png" alt="Image description" width="800" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on 'Create method'&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4p7rr2739cdkh7bp470a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4p7rr2739cdkh7bp470a.png" alt="Image description" width="800" height="287"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhju1ziukvyk9o02c5jqr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhju1ziukvyk9o02c5jqr.png" alt="Image description" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After you are done with the configurations for both API, click on 'Deploy API'&lt;/p&gt;

&lt;p&gt;4.3 Actions → Deploy API&lt;br&gt;
Deployment stage: prod&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fopzfc2zxzmawyalp02d8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fopzfc2zxzmawyalp02d8.png" alt="Image description" width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on 'Deploy'&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fohkzxgo9japnjb1fuubf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fohkzxgo9japnjb1fuubf.png" alt="Image description" width="800" height="312"&gt;&lt;/a&gt;&lt;br&gt;
After you deployment you will see a Stage.&lt;br&gt;
Note the of Invoke URL&lt;/p&gt;

&lt;p&gt;Step 5: Create Frontend&lt;br&gt;
5.1 index.html&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8"&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&amp;gt;
    &amp;lt;title&amp;gt;Event Notification System&amp;lt;/title&amp;gt;
    &amp;lt;link rel="stylesheet" href="styles.css"&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;div class="container"&amp;gt;
        &amp;lt;header&amp;gt;
            &amp;lt;h1&amp;gt;🎉 Event Notification System&amp;lt;/h1&amp;gt;
            &amp;lt;p&amp;gt;Stay updated with the latest events!&amp;lt;/p&amp;gt;
        &amp;lt;/header&amp;gt;

        &amp;lt;main&amp;gt;
            &amp;lt;!-- Subscription Section --&amp;gt;
            &amp;lt;section class="card"&amp;gt;
                &amp;lt;h2&amp;gt;📧 Subscribe to Notifications&amp;lt;/h2&amp;gt;
                &amp;lt;form id="subscriptionForm"&amp;gt;
                    &amp;lt;div class="form-group"&amp;gt;
                        &amp;lt;label for="email"&amp;gt;Email Address:&amp;lt;/label&amp;gt;
                        &amp;lt;input type="email" id="email" name="email" required&amp;gt;
                        &amp;lt;button type="submit"&amp;gt;Subscribe&amp;lt;/button&amp;gt;
                    &amp;lt;/div&amp;gt;
                &amp;lt;/form&amp;gt;
                &amp;lt;div id="subscriptionMessage" class="message"&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;/section&amp;gt;

            &amp;lt;!-- Event Creation Section --&amp;gt;
            &amp;lt;section class="card"&amp;gt;
                &amp;lt;h2&amp;gt;➕ Create New Event&amp;lt;/h2&amp;gt;
                &amp;lt;form id="eventForm"&amp;gt;
                    &amp;lt;div class="form-group"&amp;gt;
                        &amp;lt;label for="eventName"&amp;gt;Event Name:&amp;lt;/label&amp;gt;
                        &amp;lt;input type="text" id="eventName" name="eventName" required&amp;gt;
                    &amp;lt;/div&amp;gt;
                    &amp;lt;div class="form-group"&amp;gt;
                        &amp;lt;label for="eventDescription"&amp;gt;Description:&amp;lt;/label&amp;gt;
                        &amp;lt;textarea id="eventDescription" name="eventDescription" rows="3" required&amp;gt;&amp;lt;/textarea&amp;gt;
                    &amp;lt;/div&amp;gt;
                    &amp;lt;div class="form-group"&amp;gt;
                        &amp;lt;label for="eventDate"&amp;gt;Event Date:&amp;lt;/label&amp;gt;
                        &amp;lt;input type="datetime-local" id="eventDate" name="eventDate" required&amp;gt;
                    &amp;lt;/div&amp;gt;
                    &amp;lt;button type="submit"&amp;gt;Create Event &amp;amp; Notify&amp;lt;/button&amp;gt;
                &amp;lt;/form&amp;gt;
                &amp;lt;div id="eventMessage" class="message"&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;/section&amp;gt;

            &amp;lt;!-- Recent Events Section --&amp;gt;
            &amp;lt;section class="card"&amp;gt;
                &amp;lt;h2&amp;gt;📅 Recent Events&amp;lt;/h2&amp;gt;
                &amp;lt;div id="eventsList"&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;/section&amp;gt;
        &amp;lt;/main&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;script src="script.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;5.2 styles.css&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Arial', sans-serif;
    line-height: 1.6;
    color: #333;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    min-height: 100vh;
}

.container {
    max-width: 800px;
    margin: 0 auto;
    padding: 20px;
}

header {
    text-align: center;
    margin-bottom: 30px;
    color: white;
}

header h1 {
    font-size: 2.5rem;
    margin-bottom: 10px;
    text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}

header p {
    font-size: 1.2rem;
    opacity: 0.9;
}

.card {
    background: white;
    border-radius: 12px;
    padding: 25px;
    margin-bottom: 20px;
    box-shadow: 0 8px 25px rgba(0,0,0,0.1);
    transition: transform 0.3s ease;
}

.card:hover {
    transform: translateY(-2px);
}

.card h2 {
    color: #4a5568;
    margin-bottom: 20px;
    font-size: 1.5rem;
}

.form-group {
    margin-bottom: 20px;
}

.form-group label {
    display: block;
    margin-bottom: 8px;
    font-weight: bold;
    color: #4a5568;
}

.form-group input,
.form-group textarea {
    width: 100%;
    padding: 12px;
    border: 2px solid #e2e8f0;
    border-radius: 8px;
    font-size: 16px;
    transition: border-color 0.3s ease;
}

.form-group input:focus,
.form-group textarea:focus {
    outline: none;
    border-color: #667eea;
    box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}

button {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    padding: 12px 24px;
    border: none;
    border-radius: 8px;
    font-size: 16px;
    font-weight: bold;
    cursor: pointer;
    transition: all 0.3s ease;
}

button:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
}

button:disabled {
    opacity: 0.6;
    cursor: not-allowed;
    transform: none;
}

.message {
    margin-top: 15px;
    padding: 12px;
    border-radius: 8px;
    font-weight: bold;
    display: none;
}

.message.success {
    background-color: #f0fff4;
    color: #22543d;
    border: 1px solid #9ae6b4;
}

.message.error {
    background-color: #fff5f5;
    color: #742a2a;
    border: 1px solid #feb2b2;
}

.event-item {
    background-color: #f8f9fa;
    border-left: 4px solid #667eea;
    padding: 15px;
    margin-bottom: 15px;
    border-radius: 8px;
}

.event-item h3 {
    color: #4a5568;
    margin-bottom: 8px;
}

.event-item p {
    color: #718096;
    margin-bottom: 5px;
}

.event-date {
    font-weight: bold;
    color: #667eea;
}

.loading {
    text-align: center;
    color: #718096;
    font-style: italic;
}

@media (max-width: 600px) {
    .container {
        padding: 15px;
    }

    header h1 {
        font-size: 2rem;
    }

    .card {
        padding: 20px;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;5.3 script.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Replace with your actual API Gateway URL
const API_BASE_URL = 'https://your-api-id.execute-api.region.amazonaws.com/prod';

// Sample events data
let events = [];

// DOM elements
const subscriptionForm = document.getElementById('subscriptionForm');
const eventForm = document.getElementById('eventForm');
const subscriptionMessage = document.getElementById('subscriptionMessage');
const eventMessage = document.getElementById('eventMessage');
const eventsList = document.getElementById('eventsList');

// Initialize the application
document.addEventListener('DOMContentLoaded', function() {
    loadSampleEvents();
    displayEvents();

    // Form event listeners
    subscriptionForm.addEventListener('submit', handleSubscription);
    eventForm.addEventListener('submit', handleEventCreation);
});

// Handle email subscription
async function handleSubscription(e) {
    e.preventDefault();

    const email = document.getElementById('email').value;
    const submitBtn = subscriptionForm.querySelector('button[type="submit"]');

    // Show loading state
    submitBtn.disabled = true;
    submitBtn.textContent = 'Subscribing...';
    hideMessage(subscriptionMessage);

    try {
        const response = await fetch(`${API_BASE_URL}/subscribe`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ email: email })
        });

        const data = await response.json();

        if (response.ok) {
            showMessage(subscriptionMessage, data.message, 'success');
            subscriptionForm.reset();
        } else {
            showMessage(subscriptionMessage, data.error || 'Subscription failed', 'error');
        }
    } catch (error) {
        console.error('Subscription error:', error);
        showMessage(subscriptionMessage, 'Network error. Please try again.', 'error');
    } finally {
        // Reset button state
        submitBtn.disabled = false;
        submitBtn.textContent = 'Subscribe';
    }
}

// Handle event creation
async function handleEventCreation(e) {
    e.preventDefault();

    const eventName = document.getElementById('eventName').value;
    const eventDescription = document.getElementById('eventDescription').value;
    const eventDate = document.getElementById('eventDate').value;
    const submitBtn = eventForm.querySelector('button[type="submit"]');

    // Show loading state
    submitBtn.disabled = true;
    submitBtn.textContent = 'Creating Event...';
    hideMessage(eventMessage);

    try {
        const response = await fetch(`${API_BASE_URL}/create-event`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                eventName: eventName,
                eventDescription: eventDescription,
                eventDate: eventDate
            })
        });

        const data = await response.json();

        if (response.ok) {
            showMessage(eventMessage, data.message, 'success');
            eventForm.reset();

            // Add new event to the list
            events.unshift({
                name: eventName,
                description: eventDescription,
                date: eventDate,
                created_at: new Date().toISOString()
            });
            displayEvents();
        } else {
            showMessage(eventMessage, data.error || 'Event creation failed', 'error');
        }
    } catch (error) {
        console.error('Event creation error:', error);
        showMessage(eventMessage, 'Network error. Please try again.', 'error');
    } finally {
        // Reset button state
        submitBtn.disabled = false;
        submitBtn.textContent = 'Create Event &amp;amp; Notify';
    }
}

// Show message
function showMessage(element, message, type) {
    element.textContent = message;
    element.className = `message ${type}`;
    element.style.display = 'block';

    // Hide message after 5 seconds
    setTimeout(() =&amp;gt; {
        hideMessage(element);
    }, 5000);
}

// Hide message
function hideMessage(element) {
    element.style.display = 'none';
}

// Load sample events
function loadSampleEvents() {
    events = [
        {
            name: "Welcome to Event System",
            description: "This is a demo event to show how the notification system works.",
            date: "2025-06-15T10:00:00",
            created_at: "2025-06-08T08:00:00Z"
        },
        {
            name: "AWS Workshop",
            description: "Learn serverless architecture with AWS Lambda, SNS, and API Gateway.",
            date: "2025-06-20T14:00:00",
            created_at: "2025-06-07T12:00:00Z"
        }
    ];
}

// Display events
function displayEvents() {
    if (events.length === 0) {
        eventsList.innerHTML = '&amp;lt;p class="loading"&amp;gt;No events yet. Create your first event above!&amp;lt;/p&amp;gt;';
        return;
    }

    const eventsHTML = events.map(event =&amp;gt; {
        const eventDate = new Date(event.date);
        const formattedDate = eventDate.toLocaleDateString('en-US', {
            weekday: 'long',
            year: 'numeric',
            month: 'long',
            day: 'numeric',
            hour: '2-digit',
            minute: '2-digit'
        });

        return `
            &amp;lt;div class="event-item"&amp;gt;
                &amp;lt;h3&amp;gt;${event.name}&amp;lt;/h3&amp;gt;
                &amp;lt;p&amp;gt;${event.description}&amp;lt;/p&amp;gt;
                &amp;lt;p class="event-date"&amp;gt;📅 ${formattedDate}&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
        `;
    }).join('');

    eventsList.innerHTML = eventsHTML;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;5.4 events.json (Sample Data)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "events": [
        {
            "id": 1,
            "name": "Welcome to Event System",
            "description": "This is a demo event to show how the notification system works.",
            "date": "2025-06-15T10:00:00",
            "created_at": "2025-06-08T08:00:00Z"
        },
        {
            "id": 2,
            "name": "AWS Workshop",
            "description": "Learn serverless architecture with AWS Lambda, SNS, and API Gateway.",
            "date": "2025-06-20T14:00:00",
            "created_at": "2025-06-07T12:00:00Z"
        },
        {
            "id": 3,
            "name": "Cloud Security Seminar",
            "description": "Best practices for securing your cloud infrastructure.",
            "date": "2025-06-25T16:00:00",
            "created_at": "2025-06-06T10:00:00Z"
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🪣 Step 6: Deploy to S3&lt;br&gt;
6.1 Create S3 Bucket&lt;/p&gt;
&lt;h1&gt;
  
  
  Create bucket (replace with unique name)
&lt;/h1&gt;

&lt;p&gt;aws s3 mb s3://your-event-system-bucket-name&lt;/p&gt;
&lt;h1&gt;
  
  
  Or use AWS Console:
&lt;/h1&gt;
&lt;h1&gt;
  
  
  Services → S3 → Create bucket
&lt;/h1&gt;
&lt;h1&gt;
  
  
  Bucket name: your-event-system-bucket-name
&lt;/h1&gt;
&lt;h1&gt;
  
  
  Region: us-east-1 (or your preferred region)
&lt;/h1&gt;

&lt;p&gt;6.2 Enable Static Website Hosting&lt;/p&gt;

&lt;p&gt;Go to bucket properties&lt;br&gt;
Static website hosting → Edit → Enable&lt;br&gt;
Index document: index.html&lt;br&gt;
Error document: index.html&lt;/p&gt;

&lt;p&gt;6.3 Configure Bucket Policy&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::your-event-system-bucket-name/*"
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;6.2 &lt;br&gt;
You need to create a bucket to house your files, which will enable Static Website Hosting.&lt;br&gt;
On the console, type 'S3' and click on 'Create bucket'. Give your bucket a name: event-system&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49u9m0q933c1fc0azbrq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49u9m0q933c1fc0azbrq.png" alt="Image description" width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Under object Ownership, select 'ACLs disabled'&lt;br&gt;
Uncheck the 'block public access setting for this bucket'&lt;br&gt;
Enable bucket key&lt;br&gt;
Enable bucket versioning&lt;br&gt;
Click on 'Create bucket'&lt;/p&gt;

&lt;p&gt;Go to bucket properties&lt;br&gt;
Static website hosting → Edit → Enable&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqmu37wgefldscbzjh24m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqmu37wgefldscbzjh24m.png" alt="Image description" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3j17yrmf98yasi5xpodk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3j17yrmf98yasi5xpodk.png" alt="Image description" width="800" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Index document: index.html&lt;br&gt;
Error document: index.html&lt;/p&gt;

&lt;p&gt;6.3 Configure Bucket Policy&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::your-event-system-bucket-name/*"
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit your JSON 'Resource' copy and paste your bucket ARN &lt;br&gt;
arn:aws:s3:::event-systems&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcgizqymhecfhr73dy3um.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcgizqymhecfhr73dy3um.png" alt="Image description" width="800" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvh16d5udhol7mp63xqxj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvh16d5udhol7mp63xqxj.png" alt="Image description" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step 7: Configuration &amp;amp; Testing&lt;br&gt;
7.1 Update API URL&lt;br&gt;
In script.js, replace the API_BASE_URL with your actual API Gateway URL:&lt;br&gt;
javascriptconst API_BASE_URL = '&lt;a href="https://your-api-id.execute-api.us-east-1.amazon" rel="noopener noreferrer"&gt;https://your-api-id.execute-api.us-east-1.amazon&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5monml0yje1h8cc2chmc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5monml0yje1h8cc2chmc.png" alt="Image description" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;7.2 Test the System&lt;/p&gt;

&lt;p&gt;Test Email Subscription:&lt;/p&gt;

&lt;p&gt;Visit your S3 website URL&lt;br&gt;
Enter an email address&lt;br&gt;
Check email for SNS confirmation&lt;br&gt;
Confirm subscription&lt;/p&gt;

&lt;p&gt;Test Event Creation:&lt;/p&gt;

&lt;p&gt;Fill out the event form&lt;br&gt;
Submit the form&lt;br&gt;
Check subscribed emails for notifications&lt;/p&gt;

&lt;p&gt;7.3 Monitor with CloudWatch&lt;/p&gt;

&lt;p&gt;Lambda function logs&lt;br&gt;
API Gateway metrics&lt;br&gt;
SNS delivery status&lt;/p&gt;

&lt;p&gt;Troubleshooting&lt;br&gt;
Common Issues:&lt;br&gt;
CORS Errors:&lt;/p&gt;

&lt;p&gt;Ensure CORS is enabled on API Gateway&lt;br&gt;
Add proper headers in Lambda responses&lt;/p&gt;

&lt;p&gt;Lambda Timeout:&lt;/p&gt;

&lt;p&gt;Increase timeout in Lambda configuration&lt;br&gt;
Check CloudWatch logs for errors&lt;/p&gt;

&lt;p&gt;SNS Not Sending:&lt;/p&gt;

&lt;p&gt;Verify IAM permissions&lt;br&gt;
Check topic ARN in environment variables&lt;br&gt;
Confirm email subscriptions are confirmed&lt;/p&gt;

&lt;p&gt;S3 Access Denied:&lt;/p&gt;

&lt;p&gt;Check bucket policy is correct&lt;br&gt;
Ensure bucket is public for static hosting&lt;/p&gt;

&lt;p&gt;🎯 Next Steps &amp;amp; Enhancements&lt;/p&gt;

&lt;p&gt;Add DynamoDB: Store events persistently&lt;br&gt;
SMS Notifications: Add SMS support via SNS&lt;br&gt;
User Authentication: Integrate with Amazon Cognito&lt;br&gt;
Event Categories: Add filtering and categorization&lt;br&gt;
Scheduled Events: Use EventBridge for recurring notifications&lt;br&gt;
Analytics: Track user engagement with CloudWatch&lt;/p&gt;

&lt;p&gt;🔒 Security Best Practices&lt;/p&gt;

&lt;p&gt;Least Privilege: IAM roles with minimal permissions&lt;br&gt;
API Throttling: Configure in API Gateway&lt;br&gt;
Input Validation: Validate all user inputs&lt;br&gt;
HTTPS Only: Ensure all communications are encrypted&lt;br&gt;
Email Validation: Implement proper email format checking&lt;/p&gt;

&lt;p&gt;📝 Project Summary&lt;br&gt;
You've built a complete serverless event notification system using:&lt;/p&gt;

&lt;p&gt;Frontend: HTML/CSS/JS hosted on S3&lt;br&gt;
API: REST API with API Gateway&lt;br&gt;
Compute: Serverless Lambda functions&lt;br&gt;
Messaging: SNS for email notifications&lt;br&gt;
Security: IAM roles and policies&lt;/p&gt;

&lt;p&gt;This project demonstrates real-world AWS architecture patterns and can be extended for production use cases!&lt;/p&gt;

</description>
      <category>cloudcomputing</category>
      <category>cloudpractitioner</category>
      <category>solutionarchitect</category>
    </item>
    <item>
      <title>Serverless Web App Development Made Easy: A Complete Guide with AWS Amplify, DynamoDB, Lambda and API Gateway</title>
      <dc:creator>Isaac Obuor</dc:creator>
      <pubDate>Tue, 03 Jun 2025 04:41:20 +0000</pubDate>
      <link>https://dev.to/isaac_obuor_4ec2278316110/serverless-web-app-development-made-easy-a-complete-guide-with-aws-amplify-dynamodb-lambda-and-353o</link>
      <guid>https://dev.to/isaac_obuor_4ec2278316110/serverless-web-app-development-made-easy-a-complete-guide-with-aws-amplify-dynamodb-lambda-and-353o</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9mns6568ol5i7dxiavvo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9mns6568ol5i7dxiavvo.png" alt="Image description" width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Get ready to dive into the world of serverless web application development on AWS. In this series, we’ll guide you through the process of creating a dynamic web app that calculates the area of a rectangle based on user-provided length and width values. We’ll leverage the power of AWS Amplify for web hosting, AWS Lambda functions for real-time calculations, DynamoDB for storing and retrieving results, and API Gateway for seamless communication. By the end of this journey, you’ll have the skills to build a responsive and scalable solution that showcases the true potential of serverless architecture. Let’s embark on this development adventure together!&lt;/p&gt;

&lt;p&gt;Access to the project files: &lt;a href="https://github.com/obuor-developer/amplify" rel="noopener noreferrer"&gt;Amplify Web App&lt;br&gt;
&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Creating Frontend&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use the index.html file from the project files. Or simply open a text editor and copy the following code into an index.html file. Note the part with “YOUR API URL” as we will be filling this part with the API URL later.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8"&amp;gt;
    &amp;lt;title&amp;gt;Rectangle&amp;lt;/title&amp;gt;
    &amp;lt;!-- Styling for the client UI --&amp;gt;
    &amp;lt;style&amp;gt;
    h1 {
        color: #FFFFFF;
        font-family: system-ui;
  margin-left: 20px;
        }
 body {
        background-color: #222629;
        }
    label {
        color: #86C232;
        font-family: system-ui;
        font-size: 20px;
        margin-left: 20px;
  margin-top: 20px;
        }
     button {
        background-color: #86C232;
  border-color: #86C232;
  color: #FFFFFF;
        font-family: system-ui;
        font-size: 20px;
  font-weight: bold;
        margin-left: 30px;
  margin-top: 20px;
  width: 140px;
        }
  input {
        color: #222629;
        font-family: system-ui;
        font-size: 20px;
        margin-left: 10px;
  margin-top: 20px;
  width: 100px;
        }
    &amp;lt;/style&amp;gt;
    &amp;lt;script&amp;gt;
        // callAPI function that takes the length and width numbers as parameters
        var callAPI = (length,width)=&amp;gt;{
            // instantiate a headers object
            var myHeaders = new Headers();
            // add content type header to object
            myHeaders.append("Content-Type", "application/json");
            // using built in JSON utility package turn object to string and store in a variable
            var raw = JSON.stringify({"length":length,"width":width});
            // create a JSON object with parameters for API call and store in a variable
            var requestOptions = {
                method: 'POST',
                headers: myHeaders,
                body: raw,
                redirect: 'follow'
            };
            // make API call with parameters and use promises to get response
            fetch("YOUR API URL", requestOptions)
            .then(response =&amp;gt; response.text())
            .then(result =&amp;gt; alert(JSON.parse(result).body))
            .catch(error =&amp;gt; console.log('error', error));
        }
    &amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;AREA OF A RECTANGLE!&amp;lt;/h1&amp;gt;
 &amp;lt;form&amp;gt;
        &amp;lt;label&amp;gt;Length:&amp;lt;/label&amp;gt;
        &amp;lt;input type="text" id="length"&amp;gt;
        &amp;lt;label&amp;gt;Width:&amp;lt;/label&amp;gt;
        &amp;lt;input type="text" id="width"&amp;gt;
        &amp;lt;!-- set button onClick method to call function we defined passing input values as parameters --&amp;gt;
        &amp;lt;button type="button" onclick="callAPI(document.getElementById('length').value,document.getElementById('width').value)"&amp;gt;CALCULATE&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;The file should look like this when opened on a browser. It gives spaces to input the length and width of a rectangle and a ‘Calculate’ button&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4dfsswbfy5074tceca8m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4dfsswbfy5074tceca8m.png" alt="Image description" width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hosting the App on AWS Amplify&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On your AWS console search box, search for ‘Amplify’ and click on the first option that appears&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fizxke2kld95rkz6gm5bs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fizxke2kld95rkz6gm5bs.png" alt="Image description" width="800" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click on "Create new app"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fueo3ibb4pmnkufcor7f3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fueo3ibb4pmnkufcor7f3.png" alt="Image description" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;select the source for your app files. They can be in a remote repository or local. We will use ‘GitHub’ since our files are in GitHub. We also need to use a compressed folder with our files. Click on ‘Next’&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flunvdlvwfdggqiffhlci.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flunvdlvwfdggqiffhlci.png" alt="Image description" width="800" height="306"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We have to make sure the Amplify GitHub App has permission to the repository and select your file. Click on 'Next'&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbm7yj6k5l5we4o9k2yqr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbm7yj6k5l5we4o9k2yqr.png" alt="Image description" width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Name your App and edit YML file. Copy this code and paste it into the YML file. Click on 'Next' &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;version: 1&lt;br&gt;
frontend:&lt;br&gt;
  phases:&lt;br&gt;
    preBuild:&lt;br&gt;
      commands: []&lt;br&gt;
    build:&lt;br&gt;
      commands: []&lt;br&gt;
  artifacts:&lt;br&gt;
    baseDirectory: .&lt;br&gt;
    files:&lt;br&gt;
      - '*&lt;em&gt;/&lt;/em&gt;'&lt;br&gt;
  cache:&lt;br&gt;
    paths: []&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpn0v9v8t7tubbfhrtqwt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpn0v9v8t7tubbfhrtqwt.png" alt="Image description" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click on 'Save and Deploy'. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ifp5559op3aru5hvc8k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ifp5559op3aru5hvc8k.png" alt="Image description" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Once the deployment is complete, click on the Domain to access your app.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5vc0mo2mu0qaj82rad2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5vc0mo2mu0qaj82rad2.png" alt="Image description" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The app opens on the browser. (You might need to refresh the deployment page on Amplify.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0iw893xecmow4vj5p3wp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0iw893xecmow4vj5p3wp.png" alt="Image description" width="800" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Creating a Lambda Function to do the Math&lt;br&gt;
*&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On the AWS console search bar, type ‘Lambda’ and select the Lambda service&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnybakpgs634k7nvrz7gr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnybakpgs634k7nvrz7gr.png" alt="Image description" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click on 'Create a function'&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiomy4l9b8a7jhnqcnole.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiomy4l9b8a7jhnqcnole.png" alt="Image description" width="800" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Give the Function name, The Runtime(Latest Python), then scroll down and click on ‘Create function’&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0d0mel261qp8bm58b7a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0d0mel261qp8bm58b7a.png" alt="Image description" width="800" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnf0cjf67zhdqhf8mk79f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnf0cjf67zhdqhf8mk79f.png" alt="Image description" width="800" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy the following Lambda function onto your lambda_function.py file. Please note the DynamoDB name. We will be use this name later as we create the DB.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# import the JSON utility package
import json

# import the AWS SDK (for Python the package name is boto3)
import boto3

# import two packages to help us with dates and date formatting
from time import gmtime, strftime

# create a DynamoDB object using the AWS SDK
dynamodb = boto3.resource('dynamodb')

# use the DynamoDB object to select our table
table = dynamodb.Table('AreaDatabase')

# store the current time in a human readable format in a variable
now = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())

# define the handler function that the Lambda service will use an entry point
def lambda_handler(event, context):

# extract the two numbers from the Lambda service's event object
    Area = int(event['length']) * int(event['width'])

# write result and time to the DynamoDB table using the object we instantiated and save response in a variable
    response = table.put_item(
        Item={
            'ID': str(Area),
            'LatestGreetingTime':now
            })

# return a properly formatted JSON object
    return {
    'statusCode': 200,
    'body': json.dumps('Your result is ' + str(Area))
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Select 'code' and paste your lambda_function.py code. Click on 'Deploy'&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7z3bgo29dzrdibpx9jn1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7z3bgo29dzrdibpx9jn1.png" alt="Image description" width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Create an API Gateway&lt;/em&gt;_&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On the AWS services search box, enter ‘API’ and select ‘API Gateway’ that appears&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6f25pa95hwchiwqwvimp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6f25pa95hwchiwqwvimp.png" alt="Image description" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the list for ‘Choose an API type’, select ‘Create REST API’&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1dt2b3wn4eil5rjb94wj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1dt2b3wn4eil5rjb94wj.png" alt="Image description" width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3sx5uetww38znuy6k46s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3sx5uetww38znuy6k46s.png" alt="Image description" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select ‘New API’ under ‘Create new API’ and give the API a name, then click on ‘Create API’&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyyxhovc582d5m6ae153v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyyxhovc582d5m6ae153v.png" alt="Image description" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On the page that appears, select ‘Resources’ on the Left Panel, select ‘Create method’. Select ‘POST’ on the drop-down that appears, then click on the ✔. Select ‘Lambda Function’ as the Integration type and type or choose the name of the lambda function in the ‘Lambda Function’ box. Click on ‘Create method’&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi9mob2otfah5ilfwqio7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi9mob2otfah5ilfwqio7.png" alt="Image description" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg14tu8c1bsomwu9wiwkm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg14tu8c1bsomwu9wiwkm.png" alt="Image description" width="800" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvelxx91o5wrn67gkhc6n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvelxx91o5wrn67gkhc6n.png" alt="Image description" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Once all the checks are complete, click on ‘Deploy API’&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4m53hen0mrb3eqj669r8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4m53hen0mrb3eqj669r8.png" alt="Image description" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Give the ‘Stage name’, then click ‘Deploy’&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F68uxf1pa3szwjf0yrpxp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F68uxf1pa3szwjf0yrpxp.png" alt="Image description" width="800" height="616"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Invoke URL is what you replace “YOUR API URL” with on the index.html file. Insert the URL, regenerate the index.html file and reupload to Amplify&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmutl05qvz5lp4f7kmkcv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmutl05qvz5lp4f7kmkcv.png" alt="Image description" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Invoke URL: &lt;a href="https://j2hiyb31be.execute-api.us-east-1.amazonaws.com/mycloud" rel="noopener noreferrer"&gt;https://j2hiyb31be.execute-api.us-east-1.amazonaws.com/mycloud&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Setting up a Database on DynamoDB to store results&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On the services search box, search for ‘DynamoDB’ and select the DynamoDB service&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fko4hfbvo991fd5ns7pu2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fko4hfbvo991fd5ns7pu2.png" alt="Image description" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click on ‘Create table’&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fex6t0td4ls4fz7v0zuua.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fex6t0td4ls4fz7v0zuua.png" alt="Image description" width="800" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Give the table a name, for ‘Partition key’ input ‘ID’. Leave the rest as default, scroll to the bottom and click on ‘Create table’&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fihr7dphr6ma3kkoko55q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fihr7dphr6ma3kkoko55q.png" alt="Image description" width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select the table name. Under the settings tab, expand ‘General information’, then take note of the ARN&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fazm2w5jffdrzk6bjaicc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fazm2w5jffdrzk6bjaicc.png" alt="Image description" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;arn:aws:dynamodb:us-east-1:266735826605:table/web-table&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Let’s add permissions to our Lambda function to access DynamoDB. On the Lambda function window, select the ‘Configuration’ tab, then ‘Permissions’ on the left side panel, and select the Role name.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5fzs6l9fry868e4n5z8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5fzs6l9fry868e4n5z8.png" alt="Image description" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A new tab opens in IAM and we can add permissions to the role. Click on ‘Add permissions’, then ‘Create inline policy’&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp3vz0kjwt7j7psafhru4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp3vz0kjwt7j7psafhru4.png" alt="Image description" width="800" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select the JSON Tab and copy the following policy. Replace “YOUR-TABLE-ARN” with the ARN of your table that we copied in step 4, then click ‘Next’ at the bottom&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ficsqbisjqqltkkz8utes.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ficsqbisjqqltkkz8utes.png" alt="Image description" width="800" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;br&gt;
{&lt;br&gt;
"Version": "2012-10-17",&lt;br&gt;
"Statement": [&lt;br&gt;
    {&lt;br&gt;
        "Sid": "VisualEditor0",&lt;br&gt;
        "Effect": "Allow",&lt;br&gt;
        "Action": [&lt;br&gt;
            "dynamodb:PutItem",&lt;br&gt;
            "dynamodb:DeleteItem",&lt;br&gt;
            "dynamodb:GetItem",&lt;br&gt;
            "dynamodb:Scan",&lt;br&gt;
            "dynamodb:Query",&lt;br&gt;
            "dynamodb:UpdateItem"&lt;br&gt;
        ],&lt;br&gt;
        "Resource": "YOUR-TABLE-ARN"&lt;br&gt;
    }&lt;br&gt;
    ]&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On the ‘Review and create’ page, give the policy a name then click on ‘Create policy’ at the bottom of the page&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmzs8nn15it5a62ghqj1c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmzs8nn15it5a62ghqj1c.png" alt="Image description" width="800" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Now the Lambda function has permissions to write to the DB&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Testing&lt;/p&gt;

&lt;p&gt;Now that we are done, let’s see what we have. Open the AWS Amplify domain. It should open our app.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Input values for the Length and Width and click on “Calculate”. The solution should pop up on the screen. (Returned in the browser through API Gateway)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhv1kook7vfuc6pje811w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhv1kook7vfuc6pje811w.png" alt="Image description" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Conclusion&lt;/p&gt;

&lt;p&gt;In this comprehensive guide, we’ve embarked on an exciting journey into the realm of serverless web application development on AWS. We’ve built a dynamic web app that calculates the area of a rectangle based on user-provided length and width values. Leveraging the power of AWS Amplify for web hosting, AWS Lambda functions for real-time calculations, DynamoDB for result storage, and API Gateway for seamless communication, we’ve demonstrated the incredible potential of serverless architecture.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Automating User Management on Linux with Bash</title>
      <dc:creator>Isaac Obuor</dc:creator>
      <pubDate>Fri, 05 Jul 2024 19:39:39 +0000</pubDate>
      <link>https://dev.to/isaac_obuor_4ec2278316110/automating-user-management-on-linux-with-bash-1b4b</link>
      <guid>https://dev.to/isaac_obuor_4ec2278316110/automating-user-management-on-linux-with-bash-1b4b</guid>
      <description>&lt;p&gt;Managing users and groups on a Linux system can be a repetitive and error-prone task, especially in environments where users frequently join or leave the system. In this article, I'll walk you through creating a Bash script that automates user and group management, ensuring secure password handling and detailed logging.&lt;/p&gt;

&lt;p&gt;This task is part of the HNG Internship, a fantastic program that helps interns gain real-world experience. You can learn more about the program at the &lt;a href="https://hng.tech/internship" rel="noopener noreferrer"&gt;HNG Internship website&lt;/a&gt; or consider hiring some of their talented interns through the &lt;a href="https://hng.tech/hire" rel="noopener noreferrer"&gt;HNG Hire page&lt;/a&gt;.&lt;br&gt;
The source code can be found on my &lt;a href="https://github.com/Obuorcloud/linux_user_creation.git" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Introduction
&lt;/h4&gt;

&lt;p&gt;User management is a critical task for system administrators. Automating this process not only saves time but also reduces the risk of errors. This script will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create users from an input file.&lt;/li&gt;
&lt;li&gt;Assign users to specified groups.&lt;/li&gt;
&lt;li&gt;Generate secure random passwords.&lt;/li&gt;
&lt;li&gt;Log all actions for auditing purposes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Prerequisites
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;A Linux system with Bash shell.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sudo&lt;/code&gt; privileges to execute administrative commands.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;openssl&lt;/code&gt; for generating random passwords.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Script Breakdown
&lt;/h4&gt;

&lt;p&gt;Here's the script in its entirety:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Check if the input file exists&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: Input file not found."&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="c"&gt;# Ensure log and secure directories are initialized once&lt;/span&gt;
&lt;span class="nv"&gt;LOG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/log/user_management.log"&lt;/span&gt;
&lt;span class="nv"&gt;PASSWORD_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/secure/user_passwords.csv"&lt;/span&gt;
&lt;span class="c"&gt;# Initialize log file&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;sudo touch&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;sudo chown &lt;/span&gt;root:root &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="c"&gt;# Initialize password file&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PASSWORD_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /var/secure
    &lt;span class="nb"&gt;sudo touch&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PASSWORD_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;sudo chown &lt;/span&gt;root:root &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PASSWORD_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;sudo chmod &lt;/span&gt;600 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PASSWORD_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="c"&gt;# Redirect stdout and stderr to the log file&lt;/span&gt;
&lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; 2&amp;gt;&amp;amp;1
&lt;span class="c"&gt;# Function to check if user exists&lt;/span&gt;
user_exists&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &amp;amp;&amp;gt;/dev/null
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;# Function to check if a group exists&lt;/span&gt;
group_exists&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    getent group &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null 2&amp;gt;&amp;amp;1
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;# Function to check if a user is in a group&lt;/span&gt;
user_in_group&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-nG&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qw&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;# Read each line from the input file&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;';'&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; username &lt;span class="nb"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c"&gt;# Trim whitespace&lt;/span&gt;
    &lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'[:space:]'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;groups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$groups&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'[:space:]'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;# Check if the user already exists&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;user_exists &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"User &lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt; already exists."&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="c"&gt;# Create user&lt;/span&gt;
        &lt;span class="nb"&gt;sudo &lt;/span&gt;useradd &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="c"&gt;# Generate random password&lt;/span&gt;
        &lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-base64&lt;/span&gt; 12&lt;span class="si"&gt;)&lt;/span&gt;
        &lt;span class="c"&gt;# Set password for user&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$password&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sudo &lt;/span&gt;chpasswd
        &lt;span class="c"&gt;# Log actions&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"User &lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt; created. Password: &lt;/span&gt;&lt;span class="nv"&gt;$password&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="c"&gt;# Store passwords securely&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$password&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PASSWORD_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;fi&lt;/span&gt;
    &lt;span class="c"&gt;# Ensure the user's home directory and personal group exist&lt;/span&gt;
    &lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"/home/&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"/home/&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="c"&gt;# Split the groups string into an array&lt;/span&gt;
    &lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-ra&lt;/span&gt; group_array &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$groups&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="c"&gt;# Check each group&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;group &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;group_array&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
        if &lt;/span&gt;group_exists &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
            &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Group &lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="s2"&gt; exists."&lt;/span&gt;
        &lt;span class="k"&gt;else
            &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Group &lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="s2"&gt; does not exist. Creating group &lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;
            &lt;span class="nb"&gt;sudo &lt;/span&gt;groupadd &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;fi
        if &lt;/span&gt;user_in_group &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
            &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"User &lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt; is already in group &lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;
        &lt;span class="k"&gt;else
            &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Adding user &lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt; to group &lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;
            &lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$group&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;fi
    done
done&lt;/span&gt; &amp;lt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  How It Works
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Input File Check&lt;/strong&gt;: The script starts by checking if the input file exists. If not, it exits with an error message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log and Secure File Initialization&lt;/strong&gt;: It initializes the log and password files, ensuring they have the correct permissions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Function Definitions&lt;/strong&gt;: Functions to check user existence, group existence, and user membership in a group are defined.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User and Group Processing&lt;/strong&gt;: The script reads the input file line by line, processes each username and group, creates users and groups as needed, and assigns users to groups.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Password Handling&lt;/strong&gt;: Secure random passwords are generated and assigned to new users, and all actions are logged.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Running the Script
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Prepare the Input File&lt;/strong&gt;: Create a file named &lt;code&gt;input_file.txt&lt;/code&gt; with the following format:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alice;developers,admins
bob;developers
charlie;admins,users
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Make the Script Executable&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x user_management.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Run the Script&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; ./user_management.sh input_file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Conclusion
&lt;/h4&gt;

&lt;p&gt;This Bash script simplifies user management on Linux systems, ensuring users are created with secure passwords, assigned to appropriate groups, and all actions are logged for audit purposes. By automating these tasks, system administrators can save time and reduce errors.&lt;br&gt;
Feel free to customize this script further to suit your specific needs. Happy automating!&lt;/p&gt;

&lt;h4&gt;
  
  
  About the Author
&lt;/h4&gt;

&lt;h2&gt;
  
  
  Isaac Obuor is a seasoned DevOps engineer with extensive experience in automating system administration tasks. Follow Isaac Obuor on &lt;a href="https://github.com/Obuorcloud/linux_user_creation.git" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; for more insightful articles and projects.
&lt;/h2&gt;

&lt;p&gt;These comprehensive README and article drafts should help you document and share your user management script effectively across different platforms.&lt;/p&gt;

&lt;p&gt;Start your journey to becoming a world-class developer today!&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
