AWS Modern Application Workshop

Oct 4, 2025

Some notes I took while working through the AWS Modern Application Workshop. Plus a visualization of the architecture that I drew myself.

#aws #cloud #docker #container

What started as “just follow the lab” turned into a really good walkthrough of how different AWS services fit together in a real microservice setup.

For me, this workshop connected a lot of pieces:

Give it a try: github/aws-modern-application-workshop

Visualization

While drawing the architecture, the whole stack started to make sense to me. I tried to include all the actual resources we created during the deployment. Archtitecture Overview

module-1

bucket name: cldinf25-24a52511v

Preparation

bc=cldinf25-24a52511v

aws s3 mb s3://$bc

aws s3 website s3://$bc --index-document index.html

In s3 bucket > permissions > Allow public access

Edit …/module-1/aws-cli/website-bucket-policy.json

aws s3api put-bucket-policy --bucket $bc --policy file://~/environment/01-aws/module-1/aws-cli/website-bucket-policy.json

aws s3 cp ~/environment/01-aws/module-1/web/index.html s3://$bc/index.html

aws s3 cp ~/environment/01-aws/module-1/web/images s3://$bc/images --recursive

module-2: Creating a Service with AWS Fargate

{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:us-east-1:730335656277:stack/MythicalMysfitsCoreStack/c53eaff0-9a01-11f0-848a-0e3b010e3503",
            "StackName": "MythicalMysfitsCoreStack",
            "Description": "This stack deploys the core network infrastructure and IAM resources to be used for a service hosted in Amazon ECS using AWS Fargate.",
            "CreationTime": "2025-09-25T11:21:24.381000+00:00",
            "RollbackConfiguration": {},
            "StackStatus": "CREATE_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Capabilities": [
                "CAPABILITY_NAMED_IAM"
            ],
            "Outputs": [
                {
                    "OutputKey": "CurrentAccount",
                    "OutputValue": "730335656277",
                    "Description": "The ID of the Account being used.",
                    "ExportName": "MythicalMysfitsCoreStack:CurrentAccount"
                },
                {
                    "OutputKey": "FargateContainerSecurityGroup",
                    "OutputValue": "sg-09e3ee8e30eae3827",
                    "Description": "A security group used to allow Fargate containers to receive traffic",
                    "ExportName": "MythicalMysfitsCoreStack:FargateContainerSecurityGroup"
                },
                {
                    "OutputKey": "PublicSubnetOne",
                    "OutputValue": "subnet-0a0c03a59ff05ae0c",
                    "Description": "Public subnet one",
                    "ExportName": "MythicalMysfitsCoreStack:PublicSubnetOne"
                },
                {
                    "OutputKey": "PrivateSubnetTwo",
                    "OutputValue": "subnet-0d9f8ff73f358e7b1",
                    "Description": "Private subnet two",
                    "ExportName": "MythicalMysfitsCoreStack:PrivateSubnetTwo"
                },
                {
                    "OutputKey": "CurrentRegion",
                    "OutputValue": "us-east-1",
                    "Description": "The string representation of the region being used.",
                    "ExportName": "MythicalMysfitsCoreStack:CurrentRegion"
                },
                {
                    "OutputKey": "VPCId",
                    "OutputValue": "vpc-053aa64e93ea535b5",
                    "Description": "The ID of the VPC that this stack is deployed in",
                    "ExportName": "MythicalMysfitsCoreStack:VPCId"
                },
                {
                    "OutputKey": "PublicSubnetTwo",
                    "OutputValue": "subnet-0931096e9df7ba5a0",
                    "Description": "Public subnet two",
                    "ExportName": "MythicalMysfitsCoreStack:PublicSubnetTwo"
                },
                {
                    "OutputKey": "PrivateSubnetOne",
                    "OutputValue": "subnet-0f869c7f81c6ab1d6",
                    "Description": "Private subnet one",
                    "ExportName": "MythicalMysfitsCoreStack:PrivateSubnetOne"
                }
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 730335656277.dkr.ecr.us-east-1.amazonaws.com

cd ~/environment/01-aws/module-2/app

docker build -t mythicalmysfits/service .

docker tag mythicalmysfits/service:latest 730335656277.dkr.ecr.us-east-1.amazonaws.com/mythicalmysfits/service:latest

# Testrun
docker run -p 8080:8080 mythicalmysfits/service:latest

[!Tag] We tag the image so we can push it to the right ECR repository.

Push Docker Image to Amazon ECR

aws ecr create-repository --repository-name mythicalmysfits/service
{
    "repository": {
        "repositoryArn": "arn:aws:ecr:us-east-1:730335656277:repository/mythicalmysfits/service",
        "registryId": "730335656277",
        "repositoryName": "mythicalmysfits/service",
        "repositoryUri": "730335656277.dkr.ecr.us-east-1.amazonaws.com/mythicalmysfits/service",
        "createdAt": "2025-09-25T13:12:04.089000+00:00",
        "imageTagMutability": "MUTABLE",
        "imageScanningConfiguration": {
            "scanOnPush": false
        },
        "encryptionConfiguration": {
            "encryptionType": "AES256"
        }
    }
}

docker push 730335656277.dkr.ecr.us-east-1.amazonaws.com/mythicalmysfits/service:latest       
The push refers to repository [730335656277.dkr.ecr.us-east-1.amazonaws.com/mythicalmysfits/service]
aws ecr describe-images --repository-name mythicalmysfits/service

{    "imageDetails": [
        {
            "registryId": "730335656277",
            "repositoryName": "mythicalmysfits/service",
            "imageDigest": "sha256:ca6c215d986ac13459857656a9739e942f898a2d8567470fba60ba64c29443f7",
            "imageTags": [
                "latest"
            ],
            "imageSizeInBytes": 8307143,
            "imagePushedAt": "2025-09-25T13:14:52.788000+00:00",
            "imageManifestMediaType": "application/vnd.docker.distribution.manifest.v2+json",
            "artifactMediaType": "application/vnd.docker.container.image.v1+json"
        }
    ]
}
{
    "imageDetails": [
        {
            "registryId": "730335656277",
            "repositoryName": "mythicalmysfits/service",
            "imageDigest": "sha256:ca6c215d986ac13459857656a9739e942f898a2d8567470fba60ba64c29443f7",
            "imageTags": [
                "latest"
            ],
            "imageSizeInBytes": 8307143,
            "imagePushedAt": "2025-09-25T13:14:52.788000+00:00",
            "imageManifestMediaType": "application/vnd.docker.distribution.manifest.v2+json",
            "artifactMediaType": "application/vnd.docker.container.image.v1+json"
        }]}

Configuring Service Prerequisites in Amazon ECS

Amazaon Elastic Container Service: Cluster of “servers” that the service container will be deployed. We will be using Fargate, so we dont have to provision the server by ourselves.

Create ECS Cluster

aws ecs create-cluster --cluster-name MythicalMysfits-Cluster
{    "cluster": {
        "clusterArn": "arn:aws:ecs:us-east-1:730335656277:cluster/MythicalMysfits-Cluster",
        "clusterName": "MythicalMysfits-Cluster",
        "status": "ACTIVE",
        "registeredContainerInstancesCount": 0,
        "runningTasksCount": 0,
        "pendingTasksCount": 0,
        "activeServicesCount": 0,
        "statistics": [],
        "tags": [],
        "settings": [
            {
                "name": "containerInsights",
                "value": "disabled"
            }
        ],
        "capacityProviders": [],
        "defaultCapacityProviderStrategy": []
    }
}

Create AWS CloudWatch Logs Group

Needed so we can access our Logs, since we dont have access to the server infrasructure (because we are using AWS Fargate) The logs that the container generate will be pushed automatically to AWS CloudWatch.

aws logs create-log-group --log-group-name mythicalmysfits-logs

Register ECS Task Definition

RoleArn is the LabRole in the Amazon Web Interface: “IAM > Roles > LabRole”

{ "family": "mythicalmysfitsservice",
  "cpu": "256",
  "memory": "512",
  "networkMode": "awsvpc",
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "executionRoleArn": "arn:aws:iam::730335656277:role/aws-service-role/ecs.amazonaws.com/AWSServiceRoleForECS",
  "taskRoleArn": "arn:aws:iam::730335656277:role/aws-service-role/ecs.amazonaws.com/AWSServiceRoleForECS", 
  "containerDefinitions": [
    {
      "name": "MythicalMysfits-Service",
      "image": "730335656277.dkr.ecr.us-east-1.amazonaws.com/mythicalmysfits/service:latest",
      "portMappings": [
        {
          "containerPort": 8080,
          "protocol": "http"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "mythicalmysfits-logs",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "awslogs-mythicalmysfits-service"
        }
      },
      "essential": true
    }
  ]
}
aws ecs register-task-definition --cli-input-json file://~/environment/01-aws/module-2/aws-cli/task-definition.json

{
    "taskDefinition": {
        "taskDefinitionArn": "arn:aws:ecs:us-east-1:730335656277:task-definition/mythicalmysfitsservice:1",
        "containerDefinitions": [
            {
                "name": "MythicalMysfits-Service",
                "image": "730335656277.dkr.ecr.us-east-1.amazonaws.com/mythicalmysfits/service:latest",
                "cpu": 0,
{
    "taskDefinition": {
        "taskDefinitionArn": "arn:aws:ecs:us-east-1:730335656277:task-definition/mythicalmysfitsservice:1",
        "containerDefinitions": [
            {
                "name": "MythicalMysfits-Service",
                "image": "730335656277.dkr.ecr.us-east-1.amazonaws.com/mythicalmysfits/service:latest",
                "cpu": 0,
                "portMappings": [
                    {
                        "containerPort": 8080,
                        "hostPort": 8080,
                        "protocol": "tcp"
                    }
                ],
                "essential": true,
                "environment": [],
                "mountPoints": [],
                "volumesFrom": [],
                "logConfiguration": {
                    "logDriver": "awslogs",
                    "options": {
                        "awslogs-group": "mythicalmysfits-logs",
                        "awslogs-region": "us-east-1",
                        "awslogs-stream-prefix": "awslogs-mythicalmysfits-service"
                    }
                },
                "systemControls": []
            }
        ],
        "family": "mythicalmysfitsservice",
        "taskRoleArn": "arn:aws:iam::730335656277:role/aws-service-role/ecs.amazonaws.com/AWSServiceRoleForECS",
        "executionRoleArn": "arn:aws:iam::730335656277:role/aws-service-role/ecs.amazonaws.com/AWSServiceRoleForECS",
        "networkMode": "awsvpc",
        "revision": 1,
        "volumes": [],
        "status": "ACTIVE",
        "requiresAttributes": [
            {
                "name": "com.amazonaws.ecs.capability.logging-driver.awslogs"
            },
            {
                "name": "ecs.capability.execution-role-awslogs"
            },
            {
                "name": "com.amazonaws.ecs.capability.ecr-auth"
            },
            {
                "name": "com.amazonaws.ecs.capability.docker-remote-api.1.19"
            },
            {
                "name": "com.amazonaws.ecs.capability.task-iam-role"
            },
            {
                "name": "ecs.capability.execution-role-ecr-pull"
            },
            {
                "name": "com.amazonaws.ecs.capability.docker-remote-api.1.18"
            },
            {
                "name": "ecs.capability.task-eni"
            }
        ],
        "placementConstraints": [],
        "compatibilities": [
            "EC2",
            "MANAGED_INSTANCES",
            "FARGATE"
        ],
        "requiresCompatibilities": [
            "FARGATE"
        ],
        "cpu": "256",
        "memory": "512",
        "registeredAt": "2025-09-25T19:02:38.456000+00:00",
        "registeredBy": "arn:aws:sts::730335656277:assumed-role/voclabs/user4400849=agron.makolli@ost.ch"
    }
}

Fargate Load Balanced Service

Create Network Load Balancer NLB

We place a Network Load Balancer (NLB) in front of our service to make sure the service is not directly exposed to the internet. With this setup we also make sure that the frontend can always communicate to the same DNS. Which in this case ist the NLB and the service can scale up or down on demand.

aws elbv2 create-load-balancer --name mysfits-nlb --scheme internet-facing --type network --subnets subnet-0a0c03a59ff05ae0c subnet-0931096e9df7ba5a0 > ~/environment/nlb-output.json
{
    "LoadBalancers": [
        {
            "LoadBalancerArn": "arn:aws:elasticloadbalancing:us-east-1:730335656277:loadbalancer/net/mysfits-nlb/930f117e55fa705b",
            "DNSName": "mysfits-nlb-930f117e55fa705b.elb.us-east-1.amazonaws.com",
            "CanonicalHostedZoneId": "Z26RNL4JYFTOTI",
            "CreatedTime": "2025-09-25T20:59:56.174000+00:00",
            "LoadBalancerName": "mysfits-nlb",
            "Scheme": "internet-facing",
            "VpcId": "vpc-053aa64e93ea535b5",
            "State": {
                "Code": "active"
            },
            "Type": "network",
            "AvailabilityZones": [
                {
                    "ZoneName": "us-east-1a",
                    "SubnetId": "subnet-0a0c03a59ff05ae0c",
                    "LoadBalancerAddresses": []
                },
                {
                    "ZoneName": "us-east-1b",
                    "SubnetId": "subnet-0931096e9df7ba5a0",
                    "LoadBalancerAddresses": []
                }
            ],
            "IpAddressType": "ipv4",
            "EnablePrefixForIpv6SourceNat": "off"
        }
    ]
}

Error handling Load Balancer

List current load balancer

aws elbv2 describe-load-balancers --names mysfits-nlb
{
    "LoadBalancers": [
        {
            "LoadBalancerArn": "arn:aws:elasticloadbalancing:us-east-1:730335656277:loadbalancer/net/mysfits-nlb/ca927f4ee9b8bda0",
            "DNSName": "mysfits-nlb-ca927f4ee9b8bda0.elb.us-east-1.amazonaws.com",
            "CanonicalHostedZoneId": "Z26RNL4JYFTOTI",
            "CreatedTime": "2025-09-25T16:42:32.242000+00:00",
            "LoadBalancerName": "mysfits-nlb",
            "Scheme": "internet-facing",
            "VpcId": "vpc-053aa64e93ea535b5",
            "State": {
                "Code": "active"
            },
            "Type": "network",
            "AvailabilityZones": [
                {
                    "ZoneName": "us-east-1a",
                    "SubnetId": "subnet-0a0c03a59ff05ae0c",
                    "LoadBalancerAddresses": []
                },
                {
                    "ZoneName": "us-east-1b",
                    "SubnetId": "subnet-0d9f8ff73f358e7b1",
                    "LoadBalancerAddresses": []
                }
            ],
            "IpAddressType": "ipv4",
            "EnablePrefixForIpv6SourceNat": "off"
        }
    ]
}

Delete Load Balancer

aws elbv2 delete-load-balancer --load-balancer-arn <LOAD-BALANCER-ARN>

Create Load Balancer Target Group

Services/containers can register themselves in a target group so the load balancer can forward requests to them.

aws elbv2 create-target-group --name MythicalMysfits-TargetGroup --port 8080 --protocol TCP --target-type ip --vpc-id vpc-053aa64e93ea535b5 --health-check-interval-seconds 10 --health-check-path / --health-check-protocol HTTP --healthy-threshold-count 3 --unhealthy-threshold-count 3 > ~/environment/target-group-output.json
{
    "TargetGroups": [
        {
            "TargetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:730335656277:targetgroup/MythicalMysfits-TargetGroup/fcaa0e3888345e85",
            "TargetGroupName": "MythicalMysfits-TargetGroup",
            "Protocol": "TCP",
            "Port": 8080,
            "VpcId": "vpc-053aa64e93ea535b5",
            "HealthCheckProtocol": "HTTP",
            "HealthCheckPort": "traffic-port",
            "HealthCheckEnabled": true,
            "HealthCheckIntervalSeconds": 10,
            "HealthCheckTimeoutSeconds": 6,
            "HealthyThresholdCount": 3,
            "UnhealthyThresholdCount": 3,
            "HealthCheckPath": "/",
            "Matcher": {
                "HttpCode": "200-399"
            },
            "TargetType": "ip",
            "IpAddressType": "ipv4"
        }
    ]
}

Create Load Balancer Listener

Informs Load Balancer to listen to a specific port and tells him where to forward it

aws elbv2 create-listener --default-actions TargetGroupArn=arn:aws:elasticloadbalancing:us-east-1:730335656277:targetgroup/MythicalMysfits-TargetGroup/fcaa0e3888345e85,Type=forward --load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:730335656277:loadbalancer/net/mysfits-nlb/930f117e55fa705b --port 80 --protocol TCP
{
    "Listeners": [
        {
            "ListenerArn": "arn:aws:elasticloadbalancing:us-east-1:730335656277:listener/net/mysfits-nlb/ca927f4ee9b8bda0/260227d61d4f4fe8",
            "LoadBalancerArn": "arn:aws:elasticloadbalancing:us-east-1:730335656277:loadbalancer/net/mysfits-nlb/ca927f4ee9b8bda0",
            "Port": 80,
            "Protocol": "TCP",
            "DefaultActions": [
                {
                    "Type": "forward",
                    "TargetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:730335656277:targetgroup/MythicalMysfits-TargetGroup/fcaa0e3888345e85",
                    "ForwardConfig": {
                        "TargetGroups": [
                            {
                                "TargetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:730335656277:targetgroup/MythicalMysfits-TargetGroup/fcaa0e3888345e85"
                            }
                        ]
                    }
                }
            ]
        }
    ]
}

Creating a Service with Fargate

Service Linked Role for ECS

{
  "serviceName": "MythicalMysfits-Service",
  "cluster": "MythicalMysfits-Cluster",
  "launchType": "FARGATE",
  "deploymentConfiguration": {
    "maximumPercent": 200,
    "minimumHealthyPercent": 0
  },
  "desiredCount": 1,
  "networkConfiguration": {
    "awsvpcConfiguration": {
      "assignPublicIp": "DISABLED",
      "securityGroups": [
        "sg-09e3ee8e30eae3827"
      ],
      "subnets": [
        "subnet-0f869c7f81c6ab1d6",
        "subnet-0d9f8ff73f358e7b1"
      ]
    }
  },
  "taskDefinition": "mythicalmysfitsservice",
  "loadBalancers": [
    {
      "containerName": "MythicalMysfits-Service",
      "containerPort": 8080,
      "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:730335656277:targetgroup/MythicalMysfits-TargetGroup/fcaa0e3888345e85"
    }
  ]
}
aws ecs create-service --cli-input-json file://~/environment/01-aws/module-2/aws-cli/service-definition.json
{
    "service": {
        "serviceArn": "arn:aws:ecs:us-east-1:730335656277:service/MythicalMysfits-Cluster/MythicalMysfits-Service",
        "serviceName": "MythicalMysfits-Service",
        "clusterArn": "arn:aws:ecs:us-east-1:730335656277:cluster/MythicalMysfits-Cluster",
        "loadBalancers": [
            {
                "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:730335656277:targetgroup/MythicalMysfits-TargetGroup/fcaa0e3888345e85",
                "containerName": "MythicalMysfits-Service",
                "containerPort": 8080
            }
        ],
        "serviceRegistries": [],
        "status": "ACTIVE",
        "desiredCount": 1,
        "runningCount": 0,
        "pendingCount": 0,
        "launchType": "FARGATE",
        "platformVersion": "LATEST",
        "platformFamily": "Linux",
        "taskDefinition": "arn:aws:ecs:us-east-1:730335656277:task-definition/mythicalmysfitsservice:1",
        "deploymentConfiguration": {
            "deploymentCircuitBreaker": {
                "enable": false,
                "rollback": false
            },
            "maximumPercent": 200,
            "minimumHealthyPercent": 0,
            "strategy": "ROLLING",
            "bakeTimeInMinutes": 0
        },
        "deployments": [
            {
                "id": "ecs-svc/6349396091140787692",
                "status": "PRIMARY",
                "taskDefinition": "arn:aws:ecs:us-east-1:730335656277:task-definition/mythicalmysfitsservice:1",
                "desiredCount": 0,
                "pendingCount": 0,
                "runningCount": 0,
                "failedTasks": 0,
                "createdAt": "2025-09-25T19:04:50.056000+00:00",
                "updatedAt": "2025-09-25T19:04:50.056000+00:00",
                "launchType": "FARGATE",
                "platformVersion": "1.4.0",
                "platformFamily": "Linux",
                "networkConfiguration": {
                    "awsvpcConfiguration": {
                        "subnets": [
                            "subnet-0d9f8ff73f358e7b1",
                            "subnet-0a0c03a59ff05ae0c"
                        ],
                        "securityGroups": [
                            "sg-09e3ee8e30eae3827"
                        ],
                        "assignPublicIp": "DISABLED"
                    }
                },
                "rolloutState": "IN_PROGRESS",
                "rolloutStateReason": "ECS deployment ecs-svc/6349396091140787692 in progress."
            }
        ],
        "roleArn": "arn:aws:iam::730335656277:role/aws-service-role/ecs.amazonaws.com/AWSServiceRoleForECS",
        "events": [],
        "createdAt": "2025-09-25T19:04:50.056000+00:00",
        "placementConstraints": [],
        "placementStrategy": [],
        "networkConfiguration": {
            "awsvpcConfiguration": {
                "subnets": [
                    "subnet-0d9f8ff73f358e7b1",
                    "subnet-0a0c03a59ff05ae0c"
                ],
                "securityGroups": [
                    "sg-09e3ee8e30eae3827"
                ],
                "assignPublicIp": "DISABLED"
            }
        },
        "healthCheckGracePeriodSeconds": 0,
        "schedulingStrategy": "REPLICA",
        "deploymentController": {
            "type": "ECS"
        },
        "createdBy": "arn:aws:iam::730335656277:role/voclabs",
        "enableECSManagedTags": false,
        "propagateTags": "NONE",
        "enableExecuteCommand": false,
        "availabilityZoneRebalancing": "ENABLED"
    }
}

Update Mythical Mysfits to call NLB

Replace API Endpoint

var mysfitsApiEndpoint = 'http://mysfits-nlb-930f117e55fa705b.elb.us-east-1.amazonaws.com'; // example: 'http://mythi-publi-abcd12345-01234567890123.elb.us-east-1.amazonaws.com'

Upload to S3

aws s3 cp ~/environment/01-aws/module-2/web/index.html s3://cldinf25-24a52511v/index.html

Module 3 - Adding Data Tier with Amazon DynamoDB

aws dynamodb create-table --cli-input-json file://~/environment/01-aws/module-3/aws-cli/dynamodb-table.json

aws dynamodb describe-table --table-name MysfitsTable

aws dynamodb scan --table-name MysfitsTable
{
    "Items": [],
    "Count": 0,
    "ScannedCount": 0,
    "ConsumedCapacity": null
}
aws dynamodb batch-write-item --request-items file://~/environment/01-aws/module-3/aws-cli/populate-dynamodb.json

Rebuild the Docker Image

Change CORS Header Attribute in mythicalMysfitsService.go:

(*w).Header().Set("Access-Control-Allow-Origin", "http://cldinf25-24a52511v.s3-website-us-east-1.amazonaws.com")
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 730335656277.dkr.ecr.us-east-1.amazonaws.com

cd ~/environment/01-aws/module-3/app

docker build -t mythicalmysfits/service .

docker tag mythicalmysfits/service:latest 730335656277.dkr.ecr.us-east-1.amazonaws.com/mythicalmysfits/service:latest

docker push 730335656277.dkr.ecr.us-east-1.amazonaws.com/mythicalmysfits/service:latest

aws ecr describe-images --repository-name mythicalmysfits/service

Update the service so the image gets rebuilt:

aws ecs update-service \
  --cluster MythicalMysfits-Cluster \
  --service MythicalMysfits-Service \
  --force-new-deployment

Update Website Content in S3

aws s3 cp --recursive ~/environment/01-aws/module-3/web/ s3://cldinf25-24a52511v/

Module 4 - Adding User and API Features with Amazon API Gateway and AWS Cognito

Adding a User Pool for Website Users

aws cognito-idp create-user-pool --pool-name MysfitsUserPool --auto-verified-attributes email
{
    "UserPool": {
        "Id": "us-east-1_p2O5mtJQx",
        "Name": "MysfitsUserPool",
        "Policies": {
            "PasswordPolicy": {
                "MinimumLength": 8,
                "RequireUppercase": true,
                "RequireLowercase": true,
                "RequireNumbers": true,
                "RequireSymbols": true,
                "TemporaryPasswordValidityDays": 7
            },
            "SignInPolicy": {
                "AllowedFirstAuthFactors": [
                    "PASSWORD"
                ]
            }
        },
        "DeletionProtection": "INACTIVE",
        "LambdaConfig": {},
        "LastModifiedDate": "2025-09-29T17:08:11.958000+00:00",
        "CreationDate": "2025-09-29T17:08:11.958000+00:00",
        "SchemaAttributes": [
            {
                "Name": "sub",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": false,
                "Required": true,
                "StringAttributeConstraints": {
                    "MinLength": "1",
                    "MaxLength": "2048"
                }
            },
        ],
        "AutoVerifiedAttributes": [
            "email"
        ],
        "VerificationMessageTemplate": {
            "DefaultEmailOption": "CONFIRM_WITH_CODE"
        },
        "UserAttributeUpdateSettings": {
            "AttributesRequireVerificationBeforeUpdate": []
        },
        "MfaConfiguration": "OFF",
        "EstimatedNumberOfUsers": 0,
        "EmailConfiguration": {
            "EmailSendingAccount": "COGNITO_DEFAULT"
        },
        "AdminCreateUserConfig": {
            "AllowAdminCreateUserOnly": false,
            "UnusedAccountValidityDays": 7
        },
        "Arn": "arn:aws:cognito-idp:us-east-1:730335656277:userpool/us-east-1_p2O5mtJQx",
        "AccountRecoverySetting": {
            "RecoveryMechanisms": [
                {
                    "Priority": 1,
                    "Name": "verified_email"
                },
                {
                    "Priority": 2,
                    "Name": "verified_phone_number"
                }
            ]
        },
        "UserPoolTier": "ESSENTIALS"
    }
}

Create Cognito User Pool Client

aws cognito-idp create-user-pool-client --user-pool-id us-east-1_p2O5mtJQx --client-name MysfitsUserPoolClient
{
    "UserPoolClient": {
        "UserPoolId": "us-east-1_p2O5mtJQx",
        "ClientName": "MysfitsUserPoolClient",
        "ClientId": "3og76rh5f1pgtpub4vn59qhthd",
        "LastModifiedDate": "2025-09-29T17:15:39.520000+00:00",
        "CreationDate": "2025-09-29T17:15:39.520000+00:00",
        "RefreshTokenValidity": 30,
        "TokenValidityUnits": {},
        "AllowedOAuthFlowsUserPoolClient": false,
        "EnableTokenRevocation": true,
        "EnablePropagateAdditionalUserContextData": false,
        "AuthSessionValidity": 3
    }
}
aws apigateway create-vpc-link --name MysfitsApiVpcLink --target-arns arn:aws:elasticloadbalancing:us-east-1:730335656277:loadbalancer/net/mysfits-nlb/930f117e55fa705b
{
    "id": "8i80kp",
    "name": "MysfitsApiVpcLink",
    "targetArns": [
        "arn:aws:elasticloadbalancing:us-east-1:730335656277:loadbalancer/net/mysfits-nlb/930f117e55fa705b"
    ],
    "status": "PENDING"
}

Edit aws-cli/api-swagger.json

{
    "swagger": 2.0,
    "info": {
        "title": "MysfitsApi"
    },
    "securityDefinitions": {
        "MysfitsUserPoolAuthorizer": {
            "type": "apiKey",
            "name": "Authorization",
            "in": "header",
            "x-amazon-apigateway-authtype": "cognito_user_pools",
            "x-amazon-apigateway-authorizer": {
                "type": "COGNITO_USER_POOLS",
                "providerARNs": [
                    "arn:aws:cognito-idp:us-east-1:730335656277:userpool/us-east-1_p2O5mtJQx"
                ]
            }
        }
    },
    "paths": {
        "/": {
            "get": {
                "responses": {
                    "200": {
                        "description": "Default response for CORS method",
                        "headers": {
                            "Access-Control-Allow-Headers": {
                                "type": "string"
                            },
                            "Access-Control-Allow-Methods": {
                                "type": "string"
                            },
                            "Access-Control-Allow-Origin": {
                                "type": "string"
                            }
                        }
                    }
                },
                "x-amazon-apigateway-integration": {
                    "connectionType": "VPC_LINK",
                    "responses": {
                        "default": {
                            "statusCode": "200",
                            "responseParameters": {
                                "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'",
                                "method.response.header.Access-Control-Allow-Methods": "'*'",
                                "method.response.header.Access-Control-Allow-Origin": "'*'"
                            }
                        }
                    },
                    "connectionId": "8i80kp",
                    "httpMethod": "GET",
                    "type": "HTTP_PROXY",
                    "uri": "http://mysfits-nlb-930f117e55fa705b.elb.us-east-1.amazonaws.com/"
                }
            },
            "options": {
                "summary": "CORS support",
                "description": "Enable CORS by returning correct headers\n",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "CORS"
                ],
                "x-amazon-apigateway-integration": {
                    "type": "mock",
                    "requestTemplates": {
                        "application/json": "{\n  \"statusCode\" : 200\n}\n"
                    },
                    "responses": {
                        "default": {
                            "statusCode": "200",
                            "responseParameters": {
                                "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'",
                                "method.response.header.Access-Control-Allow-Methods": "'*'",
                                "method.response.header.Access-Control-Allow-Origin": "'*'"
                            },
                            "responseTemplates": {
                                "application/json": "{}\n"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Default response for CORS method",
                        "headers": {
                            "Access-Control-Allow-Headers": {
                                "type": "string"
                            },
                            "Access-Control-Allow-Methods": {
                                "type": "string"
                            },
                            "Access-Control-Allow-Origin": {
                                "type": "string"
                            }
                        }
                    }
                }
            }
        },
        "/mysfits": {
            "get": {
                "responses": {
                    "200": {
                        "description": "Default response for CORS method",
                        "headers": {
                            "Access-Control-Allow-Headers": {
                                "type": "string"
                            },
                            "Access-Control-Allow-Methods": {
                                "type": "string"
                            },
                            "Access-Control-Allow-Origin": {
                                "type": "string"
                            }
                        }
                    }
                },
                "x-amazon-apigateway-integration": {
                    "connectionType": "VPC_LINK",
                    "connectionId": "8i80kp",
                    "httpMethod": "GET",
                    "type": "HTTP_PROXY",
                    "uri": "http://mysfits-nlb-930f117e55fa705b.elb.us-east-1.amazonaws.com/mysfits",
                    "responses": {
                        "default": {
                            "statusCode": "200",
                            "responseParameters": {
                                "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'",
                                "method.response.header.Access-Control-Allow-Methods": "'*'",
                                "method.response.header.Access-Control-Allow-Origin": "'*'"
                            }
                        }
                    }
                }
            },
            "options": {
                "summary": "CORS support",
                "description": "Enable CORS by returning correct headers\n",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "CORS"
                ],
                "x-amazon-apigateway-integration": {
                    "type": "mock",
                    "requestTemplates": {
                        "application/json": "{\n  \"statusCode\" : 200\n}\n"
                    },
                    "responses": {
                        "default": {
                            "statusCode": "200",
                            "responseParameters": {
                                "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'",
                                "method.response.header.Access-Control-Allow-Methods": "'*'",
                                "method.response.header.Access-Control-Allow-Origin": "'*'"
                            },
                            "responseTemplates": {
                                "application/json": "{}\n"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Default response for CORS method",
                        "headers": {
                            "Access-Control-Allow-Headers": {
                                "type": "string"
                            },
                            "Access-Control-Allow-Methods": {
                                "type": "string"
                            },
                            "Access-Control-Allow-Origin": {
                                "type": "string"
                            }
                        }
                    }
                }
            }
        },
        "/mysfits/{mysfitId}": {
            "get": {
                "parameters": [{
                    "name": "mysfitId",
                    "in": "path",
                    "required": true,
                    "type": "string"
                }],
                "responses": {
                    "200": {
                        "description": "Default response for CORS method",
                        "headers": {
                            "Access-Control-Allow-Headers": {
                                "type": "string"
                            },
                            "Access-Control-Allow-Methods": {
                                "type": "string"
                            },
                            "Access-Control-Allow-Origin": {
                                "type": "string"
                            }
                        }
                    }
                },
                "x-amazon-apigateway-integration": {
                    "requestParameters": {
                        "integration.request.path.mysfitId": "method.request.path.mysfitId"
                    },
                    "connectionType": "VPC_LINK",
                    "connectionId": "8i80kp",
                    "httpMethod": "GET",
                    "type": "HTTP_PROXY",
                    "uri": "http://mysfits-nlb-930f117e55fa705b.elb.us-east-1.amazonaws.com/mysfits/{mysfitId}",
                    "responses": {
                        "default": {
                            "statusCode": "200",
                            "responseParameters": {
                                "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'",
                                "method.response.header.Access-Control-Allow-Methods": "'*'",
                                "method.response.header.Access-Control-Allow-Origin": "'*'"
                            }
                        }
                    }
                }
            },
            "options": {
                "summary": "CORS support",
                "description": "Enable CORS by returning correct headers\n",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "CORS"
                ],
                "x-amazon-apigateway-integration": {
                    "type": "mock",
                    "requestTemplates": {
                        "application/json": "{\n  \"statusCode\" : 200\n}\n"
                    },
                    "responses": {
                        "default": {
                            "statusCode": "200",
                            "responseParameters": {
                                "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'",
                                "method.response.header.Access-Control-Allow-Methods": "'*'",
                                "method.response.header.Access-Control-Allow-Origin": "'*'"
                            },
                            "responseTemplates": {
                                "application/json": "{}\n"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Default response for CORS method",
                        "headers": {
                            "Access-Control-Allow-Headers": {
                                "type": "string"
                            },
                            "Access-Control-Allow-Methods": {
                                "type": "string"
                            },
                            "Access-Control-Allow-Origin": {
                                "type": "string"
                            }
                        }
                    }
                }
            }
        },
        "/mysfits/{mysfitId}/adopt": {
            "post": {
                "parameters": [{
                    "name": "mysfitId",
                    "in": "path",
                    "required": true,
                    "type": "string"
                }],
                "responses": {
                    "200": {
                        "description": "Default response for CORS method",
                        "headers": {
                            "Access-Control-Allow-Headers": {
                                "type": "string"
                            },
                            "Access-Control-Allow-Methods": {
                                "type": "string"
                            },
                            "Access-Control-Allow-Origin": {
                                "type": "string"
                            }
                        }
                    }
                },
                "security": [{
                    "MysfitsUserPoolAuthorizer": [

                    ]
                }],
                "x-amazon-apigateway-integration": {
                    "requestParameters": {
                        "integration.request.path.mysfitId": "method.request.path.mysfitId"
                    },
                    "connectionType": "VPC_LINK",
                    "connectionId": "8i80kp",
                    "httpMethod": "POST",
                    "type": "HTTP_PROXY",
                    "uri": "http://mysfits-nlb-930f117e55fa705b.elb.us-east-1.amazonaws.com/mysfits/{mysfitId}/adopt",
                    "responses": {
                        "default": {
                            "statusCode": "200",
                            "responseParameters": {
                                "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'",
                                "method.response.header.Access-Control-Allow-Methods": "'*'",
                                "method.response.header.Access-Control-Allow-Origin": "'*'"
                            }
                        }
                    }
                }
            },
            "options": {
                "summary": "CORS support",
                "description": "Enable CORS by returning correct headers\n",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "CORS"
                ],
                "x-amazon-apigateway-integration": {
                    "type": "mock",
                    "requestTemplates": {
                        "application/json": "{\n  \"statusCode\" : 200\n}\n"
                    },
                    "responses": {
                        "default": {
                            "statusCode": "200",
                            "responseParameters": {
                                "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'",
                                "method.response.header.Access-Control-Allow-Methods": "'*'",
                                "method.response.header.Access-Control-Allow-Origin": "'*'"
                            },
                            "responseTemplates": {
                                "application/json": "{}\n"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Default response for CORS method",
                        "headers": {
                            "Access-Control-Allow-Headers": {
                                "type": "string"
                            },
                            "Access-Control-Allow-Methods": {
                                "type": "string"
                            },
                            "Access-Control-Allow-Origin": {
                                "type": "string"
                            }
                        }
                    }
                }
            }
        },
        "/mysfits/{mysfitId}/like": {
            "post": {
                "parameters": [{
                    "name": "mysfitId",
                    "in": "path",
                    "required": true,
                    "type": "string"
                }],
                "responses": {
                    "200": {
                        "description": "Default response for CORS method",
                        "headers": {
                            "Access-Control-Allow-Headers": {
                                "type": "string"
                            },
                            "Access-Control-Allow-Methods": {
                                "type": "string"
                            },
                            "Access-Control-Allow-Origin": {
                                "type": "string"
                            }
                        }
                    }
                },
                "security": [{
                    "MysfitsUserPoolAuthorizer": [

                    ]
                }],
                "x-amazon-apigateway-integration": {
                    "responses": {
                        "default": {
                            "statusCode": "200",
                            "responseParameters": {
                                "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'",
                                "method.response.header.Access-Control-Allow-Methods": "'*'",
                                "method.response.header.Access-Control-Allow-Origin": "'*'"
                            }
                        }
                    },
                    "requestParameters": {
                        "integration.request.path.mysfitId": "method.request.path.mysfitId"
                    },
                    "connectionType": "VPC_LINK",
                    "connectionId": "8i80kp",
                    "httpMethod": "POST",
                    "security": [{
                        "MysfitsUserPoolAuthorizer": [

                        ]
                    }],
                    "type": "HTTP_PROXY",
                    "uri": "http://mysfits-nlb-930f117e55fa705b.elb.us-east-1.amazonaws.com/mysfits/{mysfitId}/like",
                    "responses": {
                        "default": {
                            "statusCode": "200",
                            "responseParameters": {
                                "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'",
                                "method.response.header.Access-Control-Allow-Methods": "'*'",
                                "method.response.header.Access-Control-Allow-Origin": "'*'"
                            }
                        }
                    }
                }
            },
            "options": {
                "summary": "CORS support",
                "description": "Enable CORS by returning correct headers\n",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "tags": [
                    "CORS"
                ],
                "x-amazon-apigateway-integration": {
                    "type": "mock",
                    "requestTemplates": {
                        "application/json": "{\n  \"statusCode\" : 200\n}\n"
                    },
                    "responses": {
                        "default": {
                            "statusCode": "200",
                            "responseParameters": {
                                "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'",
                                "method.response.header.Access-Control-Allow-Methods": "'*'",
                                "method.response.header.Access-Control-Allow-Origin": "'*'"
                            },
                            "responseTemplates": {
                                "application/json": "{}\n"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Default response for CORS method",
                        "headers": {
                            "Access-Control-Allow-Headers": {
                                "type": "string"
                            },
                            "Access-Control-Allow-Methods": {
                                "type": "string"
                            },
                            "Access-Control-Allow-Origin": {
                                "type": "string"
                            }
                        }
                    }
                }
            }
        }
    }
}

Create REST API using Swagger

Following the tutorial i got the error:

aws apigateway import-rest-api --parameters endpointConfigurationTypes=REGIONAL --body file://~/environment/01-aws/module-4/aws-cli/api-swagger.json --fail-on-warnings

Invalid base64: "{
    "swagger": 2.0,
    "info": {

To use JSON files you need to specify it with -body fileb://

aws apigateway import-rest-api --parameters endpointConfigurationTypes=REGIONAL --body fileb://~/environment/01-aws/module-4/aws-cli/api-swagger.json --fail-on-warnings                                                                                            
{
    "id": "vuj4n7mexi",
    "name": "MysfitsApi",
    "createdDate": "2025-09-29T17:41:04+00:00",
    "apiKeySource": "HEADER",
    "endpointConfiguration": {
        "types": [
            "REGIONAL"
        ],
        "ipAddressType": "ipv4"
    },
    "disableExecuteApiEndpoint": false,
    "rootResourceId": "crradh7hd6"
}

Deploy the API

aws apigateway create-deployment --rest-api-id vuj4n7mexi --stage-name prod

{
    "id": "yjy8g3",
    "createdDate": "2025-09-29T17:49:46+00:00"
}

https://vuj4n7mexi.execute-api.us-east-1.amazonaws.com/prod

    var mysfitsApiEndpoint = 'http://mysfits-nlb-930f117e55fa705b.elb.us-east-1.amazonaws.com'; // example: 'https://abcd12345.execute-api.us-east-1.amazonaws.com/prod'
    var cognitoUserPoolId = 'us-east-1_p2O5mtJQx';  // example: 'us-east-1_abcd12345'
    var cognitoUserPoolClientId = '3og76rh5f1pgtpub4vn59qhthd'; // example: 'abcd12345abcd12345abcd12345'
    var awsRegion = 'us-east-1'; // example: 'us-east-1' or 'eu-west-1' etc.

confirm.html & register.html

    var cognitoUserPoolId = 'us-east-1_p2O5mtJQx';  // example: 'us-east-1_abcd12345'
    var cognitoUserPoolClientId = '3og76rh5f1pgtpub4vn59qhthd'; // example: 'abcd12345abcd12345abcd12345'
aws s3 cp --recursive ~/environment/01-aws/module-4/web/ s3://cldinf25-24a52511v/