├── 1-lab-lambdaDynamoDB ├── README.md ├── Workshop-REST API-Page-1.png ├── lab1-test-dynamodb-list.png ├── lab1-test-dynamodb.png ├── source │ ├── cdk │ │ ├── .gitignore │ │ ├── app.py │ │ ├── cdk.json │ │ └── requirements.txt │ └── lambda-functions │ │ ├── list-data │ │ └── app.py │ │ └── store-data │ │ ├── .gitignore │ │ └── app.py └── work │ ├── README.md │ ├── cdk │ ├── .gitignore │ ├── app.py │ └── requirements.txt │ └── lambda-functions │ ├── list-data │ └── app.py │ └── store-data │ ├── .gitignore │ └── app.py ├── 2-lab-apiGatewayIntegration ├── README.md ├── Workshop-REST API-Page-2.png ├── source │ ├── cdk │ │ ├── .gitignore │ │ ├── app.py │ │ ├── cdk.context.json │ │ ├── cdk.json │ │ └── requirements.txt │ ├── lambda-functions │ │ ├── delete-data │ │ │ └── app.py │ │ ├── get-data │ │ │ └── app.py │ │ ├── list-data │ │ │ └── app.py │ │ └── save-data │ │ │ └── app.py │ └── testing-api │ │ ├── delete-data.sh │ │ ├── get-data.sh │ │ ├── list-data.sh │ │ ├── save-data.json │ │ └── save-data.sh └── work │ ├── README.md │ ├── cdk │ ├── app.py │ └── requirements.txt │ ├── lambda-functions │ ├── delete-data │ │ └── app.py │ ├── get-data │ │ └── app.py │ ├── list-data │ │ └── app.py │ └── save-data │ │ └── app.py │ └── testing-api │ ├── delete-data.sh │ ├── get-data.sh │ ├── list-data.sh │ ├── save-data.json │ └── save-data.sh ├── 3-lab-controlAccessAPI ├── README.md ├── lab3-diagram.png ├── source │ ├── cdk │ │ ├── README.md │ │ ├── app.py │ │ ├── cdk.json │ │ └── requirements.txt │ └── lambda-functions │ │ ├── secure-api │ │ └── app.py │ │ └── token-authorizer │ │ ├── app.py │ │ └── requirements.txt └── work │ ├── README.md │ ├── cdk │ ├── .gitignore │ └── app.py │ └── lambda-functions │ ├── secure-api │ └── app.py │ └── token-authorizer │ ├── app.py │ └── requirements.txt ├── LICENSE └── README.md /1-lab-lambdaDynamoDB/README.md: -------------------------------------------------------------------------------- 1 | # Lab 1: Storing and Retrieving Data from Amazon DynamoDB 2 | In this workshop, you will build 2 AWS Lambda Functions. One AWS Lambda Function will store data, and another Lambda Function to retrieve all data from a single table in Amazon DynamoDB. This is a basic exercise to help you understand how to perform basic data storing and retrieval from Lambda functions. All of resources needed in this lab will be provisioned by AWS Cloud Development Kit (CDK). 3 | 4 | ## Diagram 5 | ![Lab 1 Diagram](https://raw.githubusercontent.com/donnieprakoso/workshop-restAPI/main/1-lab-lambdaDynamoDB/Workshop-REST%20API-Page-1.png) 6 | 7 | ## Tasks 8 | These are the tasks that you need to complete. If at some point you are stuck, please refer to the primary reference located at `source/` folder. 9 | 10 | ### Step 0: Prepare work folder and boto3 11 | #### Install boto3 library 12 | - Open your terminal 13 | - Run this command 14 | ```bash 15 | pip install boto3 16 | ``` 17 | 18 | #### Preparing work folder 19 | - Navigate to `work/` folder 20 | - You will find 2 sub-directories named `cdk` and `lambda-functions` 21 | - Navigate to `work/lambda-functions/` 22 | - You will find another 2 sub-directories named `list-data` and `store-data` 23 | 24 | ### Step 1: Open Store Data Function 25 | - Navigate to `work/lambda-functions/store-data` 26 | - Open `app.py` 27 | - Read the file thoroughly as starting from next steps, you need to work on this file. 28 | 29 | 30 | ### Step 2: Saving Data to DynamoDB 31 | - Save data to DynamoDB with following info: 32 | - ID: generated random UUID string 33 | - date_created: datetime with format `%Y-%m-%d, %H:%M:%S` 34 | 35 | >**💡 HINT** 36 | >- You need to create a client to connect to AWS resources. On Python, you need to use boto3 library. 37 | >- Use put_item() API to save data into DynamoDB. Here's the [link](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html). 38 | 39 | > ### 😕 Are you stuck? 40 | > See the solution [here](https://github.com/donnieprakoso/workshop-restAPI/blob/main/1-lab-lambdaDynamoDB/source/lambda-functions/store-data/app.py) 41 | 42 | ### Step 3: Create Lambda Function to Retrieve Data 43 | - Navigate to `work/lambda-functions/list-data` 44 | - Open `app.py` 45 | - Get all data from DynamoDB and make sure that you put all the results into a variable called response 46 | - Return the Lambda function using the defined format. 47 | 48 | >**💡 HINT** 49 | >- Use scan() API to get all data from DynamoDB. Here's the [link](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#DynamoDB.Client.scan). 50 | 51 | 52 | > ### 😕 Are you stuck? 53 | > See the solution [here](https://github.com/donnieprakoso/workshop-restAPI/blob/main/1-lab-lambdaDynamoDB/source/lambda-functions/list-data/app.py) 54 | 55 | ### Step 4: Create an AWS CDK app 56 | - Navigate to `work/cdk/` 57 | - Create a file named `cdk.json` 58 | - Open `cdk.json` and write these lines. These lines are to give a set of instruction for AWS CDK on how to build this app. 59 | ```json 60 | { 61 | "app":"python3 app.py", 62 | "context":{} 63 | } 64 | ``` 65 | 66 | ### Step 5: Read all [TASK] on CDK app file 67 | - Open `app.py`. In this file, you see that we are missing few things to make this as a complete code. Read the file thoroughly and go to the next steps below. 68 | 69 | ### Step 6: Define Amazon DynamoDB 70 | - The first task on the CDK app is to create DynamoDB table. Create a CDK resource — called construct in CDK — for DynamoDB using following info: 71 | - id="-data" 72 | - table_name="-data" 73 | - partition_key=Use the following snippet: 74 | ``` 75 | python 76 | _ddb.Attribute(name='ID', type=_ddb.AttributeType.STRING) 77 | ``` 78 | - removal_policy=core.RemovalPolicy.DESTROY (NOTE: This setting is not for production) 79 | - read_capacity=1 80 | - write_capacity=1 81 | 82 | >**💡 HINT** 83 | >- AWS CDK already provides a construct for DynamoDB Table. Here's the [link](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_dynamodb/Table.html). 84 | 85 | > ### 😕 Are you stuck? 86 | > See the solution [here](https://github.com/donnieprakoso/workshop-restAPI/blob/main/1-lab-lambdaDynamoDB/source/cdk/app.py) 87 | 88 | ### Step 7: Define IAM Permissions 89 | - For Lambda functions able to perform activities with DynamoDB, it requires a few IAM permissions. 90 | - Below are the list of IAM actions needed for this workshop and it's recommended to narrow down the IAM permissions. 91 | - dynamodb:PutItem 92 | - dynamodb:GetItem 93 | - dynamodb:Scan 94 | - dynamodb:Query 95 | - dynamodb:ConditionCheckItem 96 | 97 | >**💡 HINT** 98 | >- To specify allowed actions, you can use add_action() API on PolicyStatement. Here's the [link](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_iam/PolicyStatement.html#aws_cdk.aws_iam.PolicyStatement.add_actions). 99 | 100 | > ### 😕 Are you stuck? 101 | > See the solution [here](https://github.com/donnieprakoso/workshop-restAPI/blob/main/1-lab-lambdaDynamoDB/source/cdk/app.py) 102 | 103 | ### Step 8: Add DynamoDB ARN into Policy Statement 104 | - Once that you've add all above IAM permissions, then we need to add the DynamoDB table into the policy statement 105 | dynamodb_policy_statement.add_resources(ddb_table.table_arn) 106 | 107 | >**💡 HINT** 108 | >- To specify resource(s) you can use add_resources() API on PolicyStatement. Here's the [link](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_iam/PolicyStatement.html#aws_cdk.aws_iam.PolicyStatement.add_resources). 109 | 110 | > ### 😕 Are you stuck? 111 | > See the solution [here](https://github.com/donnieprakoso/workshop-restAPI/blob/main/1-lab-lambdaDynamoDB/source/cdk/app.py) 112 | 113 | ### Step 9: Define AWS Lambda Construct for list-data 114 | - Create AWS Lambda Construct to list all data in DynamoDB. This is an exercise for you to define the AWS Lambda construct, and you can follow the fully working code in the store-data function. Use following info to build the construct: 115 | - code = Use this folder "../lambda-functions/list-data" 116 | - handler = "app.handler" 117 | - role = lambda_role 118 | - timeout= 60 seconds 119 | - runtime=Python 3.8 120 | 121 | >**💡 HINT** 122 | >- To create the function, you can use construct defined [here.](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_lambda/Function.html). 123 | 124 | > ### 😕 Are you stuck? 125 | > See the solution [here](https://github.com/donnieprakoso/workshop-restAPI/blob/main/1-lab-lambdaDynamoDB/source/cdk/app.py) 126 | 127 | 128 | ### Step 10: Add Environment Variable of DynamoDB Table for List Data function 129 | - Add environment_variable called "TABLE_NAME" and set the value to the table name of the DynamoDB. 130 | 131 | >**💡 HINT** 132 | >- Here's the [add_environment() API.](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_lambda/Function.html#aws_cdk.aws_lambda.Function.add_environment). 133 | 134 | > ### 😕 Are you stuck? 135 | > See the solution [here](https://github.com/donnieprakoso/workshop-restAPI/blob/main/1-lab-lambdaDynamoDB/source/cdk/app.py) 136 | 137 | ### Step 11: Tagging your AWS CDK App 138 | - Go to the last line of `app.py` and add these following lines. It's pretty handy to keep these lines so you can tag your CDK app. 139 | ```python 140 | core.Tags.of(stack).add('Name',stack_prefix) 141 | app.synth() 142 | ``` 143 | ### Step 11: Install all required libraries to build CDK app 144 | - Navigate to `work/cdk/` 145 | - Open the file named `requirements.txt`. This is a standard method to install any dependencies for Python applications. 146 | - Add these lines: 147 | ``` 148 | aws-cdk.core==1.96.0 149 | aws-cdk.aws-lambda==1.96.0 150 | aws-cdk.aws-dynamodb==1.96.0 151 | aws-cdk.aws-iam==1.96.0 152 | ``` 153 | - Install the libraries by executing: 154 | ```bash 155 | pip3 install -r requirements.txt 156 | ``` 157 | >**💡 HINT** 158 | >- For more information, click on the following link to find out how to install Python requirements using pip. [Link](https://pip.pypa.io/en/stable/user_guide/#requirements-files) 159 | 160 | ### Step 12: Deploy 161 | - Open terminal and navigate to `work/cdk/` 162 | - Deploy the app by executing: 163 | ```bash 164 | cdk deploy 165 | ``` 166 | ### Step 13: Testing store-data Function 167 | - Navigate to AWS Lambda Functions **store-data** [dashboard](https://ap-southeast-1.console.aws.amazon.com/lambda/home). 168 | - Make sure that you're in the same AWS region as you deployed your application. 169 | - Find the **store-data** Lambda function. To filter based on the name, you can copy and paste the function name from the CDK output or you can also filter using the stack prefix: "restAPI-lab1-lambdaDynamoDB" 170 | - Open the link which will navigate you to the AWS Lambda Function 171 | 172 | #### Invoking store-data Function 173 | Now we are going to test the store data function which will save the data into Amazon DynamoDB. 174 | 175 | - Create a test event by choosing **Test** in the upper right corner 176 | - In the Configure test event page, choose **Create new test event** and in Event template, leave the default Hello World option. Enter an Event name and provide an empty template: 177 | ```json 178 | {} 179 | ``` 180 | - Choose **Create** and then choose **Test**. 181 | 182 | #### Let's check the record on Amazon DynamoDB 183 | - Navigate to Amazon DynamoDB [dashboard](https://ap-southeast-1.console.aws.amazon.com/dynamodb/home?region=ap-southeast-1#tables:). 184 | - Find the table. To filter based on the name, you can copy and paste the table name from the CDK output or you can also filter using the stack prefix: "restAPI-lab1-lambdaDynamoDB" 185 | - If you see there's a record with data from the store-data function, then your Lambda function successfully performed activities with DynamoDB. 186 | 187 | ![Testing store-data Function](https://raw.githubusercontent.com/donnieprakoso/workshop-restAPI/main/1-lab-lambdaDynamoDB/lab1-test-dynamodb.png) 188 | 189 | ### Step 14: Testing list-data Function 190 | - Navigate to AWS Lambda Functions **store-data** [dashboard](https://ap-southeast-1.console.aws.amazon.com/lambda/home). 191 | - Make sure that you're in the same AWS region as you deployed your application. 192 | - Find the **list-data** Lambda function. To filter based on the name, you can copy and paste the function name from the CDK output or you can also filter using the stack prefix: "restAPI-lab1-lambdaDynamoDB" 193 | - Open the link which will navigate you to the AWS Lambda Function 194 | 195 | #### Invoking list-data Function 196 | Now we are going to test the store data function which will save the data into Amazon DynamoDB. 197 | 198 | - Create a test event by choosing **Test** in the upper right corner 199 | - In the Configure test event page, choose **Create new test event** and in Event template, leave the default Hello World option. Enter an Event name and provide an empty template: 200 | ```json 201 | {} 202 | ``` 203 | - Choose **Create** and then choose **Test**. 204 | - If you see the execution results similar to the image below, then your Lambda function successfully retrieved all data from DynamoDB 205 | 206 | ![Testing list-data Function](https://raw.githubusercontent.com/donnieprakoso/workshop-restAPI/main/1-lab-lambdaDynamoDB/lab1-test-dynamodb-list.png) 207 | 208 | # 🤘🏻Congrats! 209 | You've just finished the Lab 1. 210 | 211 | ## Cleaning Up 212 | To clean up all resources, follow these instructions below: 213 | 1. Go to `work/cdk/` 214 | 2. Run `cdk destroy` command 215 | ```bash 216 | cdk destroy 217 | ``` 218 | 219 | -------------------------------------------------------------------------------- /1-lab-lambdaDynamoDB/Workshop-REST API-Page-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donnieprakoso/workshop-restAPI/b3287d5749b65648710dde4e736ba55b73371c6b/1-lab-lambdaDynamoDB/Workshop-REST API-Page-1.png -------------------------------------------------------------------------------- /1-lab-lambdaDynamoDB/lab1-test-dynamodb-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donnieprakoso/workshop-restAPI/b3287d5749b65648710dde4e736ba55b73371c6b/1-lab-lambdaDynamoDB/lab1-test-dynamodb-list.png -------------------------------------------------------------------------------- /1-lab-lambdaDynamoDB/lab1-test-dynamodb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donnieprakoso/workshop-restAPI/b3287d5749b65648710dde4e736ba55b73371c6b/1-lab-lambdaDynamoDB/lab1-test-dynamodb.png -------------------------------------------------------------------------------- /1-lab-lambdaDynamoDB/source/cdk/.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | package-lock.json 3 | __pycache__ 4 | .pytest_cache 5 | .env 6 | *.egg-info 7 | 8 | # CDK asset staging directory 9 | .cdk.staging 10 | cdk.out 11 | -------------------------------------------------------------------------------- /1-lab-lambdaDynamoDB/source/cdk/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from aws_cdk import aws_iam as _iam 4 | from aws_cdk import aws_lambda as _lambda 5 | from aws_cdk import aws_dynamodb as _ddb 6 | from aws_cdk import core 7 | 8 | 9 | class CdkStack(core.Stack): 10 | def __init__(self, scope: core.Construct, id: str, stack_prefix:str, **kwargs) -> None: 11 | super().__init__(scope, id, **kwargs) 12 | 13 | # Model all required resources 14 | ddb_table = _ddb.Table( 15 | self, 16 | id='{}-data'.format(stack_prefix), 17 | table_name='{}-data'.format(stack_prefix), 18 | partition_key=_ddb.Attribute(name='ID', 19 | type=_ddb.AttributeType.STRING), 20 | removal_policy=core.RemovalPolicy.DESTROY, # THIS IS NOT RECOMMENDED FOR PRODUCTION USE 21 | read_capacity=1, 22 | write_capacity=1) 23 | 24 | ## IAM Roles 25 | lambda_role = _iam.Role( 26 | self, 27 | id='{}-lambda-role'.format(stack_prefix), 28 | assumed_by=_iam.ServicePrincipal('lambda.amazonaws.com')) 29 | 30 | cw_policy_statement = _iam.PolicyStatement(effect=_iam.Effect.ALLOW) 31 | cw_policy_statement.add_actions("logs:CreateLogGroup") 32 | cw_policy_statement.add_actions("logs:CreateLogStream") 33 | cw_policy_statement.add_actions("logs:PutLogEvents") 34 | cw_policy_statement.add_actions("logs:DescribeLogStreams") 35 | cw_policy_statement.add_resources("*") 36 | lambda_role.add_to_policy(cw_policy_statement) 37 | 38 | # Add role for DynamoDB 39 | dynamodb_policy_statement = _iam.PolicyStatement( 40 | effect=_iam.Effect.ALLOW) 41 | dynamodb_policy_statement.add_actions("dynamodb:PutItem") 42 | dynamodb_policy_statement.add_actions("dynamodb:GetItem") 43 | dynamodb_policy_statement.add_actions("dynamodb:Scan") 44 | dynamodb_policy_statement.add_actions("dynamodb:Query") 45 | dynamodb_policy_statement.add_actions("dynamodb:ConditionCheckItem") 46 | dynamodb_policy_statement.add_resources(ddb_table.table_arn) 47 | lambda_role.add_to_policy(dynamodb_policy_statement) 48 | 49 | 50 | ## AWS Lambda Functions 51 | fnLambda_storeData = _lambda.Function( 52 | self, 53 | "{}-function-storeData".format(stack_prefix), 54 | code=_lambda.AssetCode("../lambda-functions/store-data"), 55 | handler="app.handler", 56 | timeout=core.Duration.seconds(60), 57 | role=lambda_role, 58 | runtime=_lambda.Runtime.PYTHON_3_8) 59 | fnLambda_storeData.add_environment("TABLE_NAME", ddb_table.table_name) 60 | 61 | fnLambda_listData = _lambda.Function( 62 | self, 63 | "{}-function-getData".format(stack_prefix), 64 | code=_lambda.AssetCode("../lambda-functions/list-data"), 65 | handler="app.handler", 66 | role=lambda_role, 67 | timeout=core.Duration.seconds(60), 68 | runtime=_lambda.Runtime.PYTHON_3_8) 69 | fnLambda_listData.add_environment("TABLE_NAME", ddb_table.table_name) 70 | 71 | core.CfnOutput(self, "{}-output-dynamodbTable".format(stack_prefix), value=ddb_table.table_name, export_name="{}-ddbTable".format(stack_prefix)) 72 | core.CfnOutput(self, "{}-output-lambdaStoreData".format(stack_prefix), value=fnLambda_storeData.function_name, export_name="{}-lambdaStoreDataName".format(stack_prefix)) 73 | core.CfnOutput(self, "{}-output-lambdaListData".format(stack_prefix), value=fnLambda_listData.function_name, export_name="{}-lambdaListDataName".format(stack_prefix)) 74 | 75 | stack_prefix='restAPI-lab1-lambdaDynamoDB' 76 | app = core.App() 77 | stack = CdkStack(app, stack_prefix, stack_prefix=stack_prefix) 78 | core.Tags.of(stack).add('Name',stack_prefix) 79 | 80 | app.synth() 81 | -------------------------------------------------------------------------------- /1-lab-lambdaDynamoDB/source/cdk/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "python3 app.py", 3 | "context": { 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /1-lab-lambdaDynamoDB/source/cdk/requirements.txt: -------------------------------------------------------------------------------- 1 | aws-cdk.core==1.96.0 2 | aws-cdk.aws-lambda==1.96.0 3 | aws-cdk.aws-dynamodb==1.96.0 4 | aws-cdk.aws-iam==1.96.0 -------------------------------------------------------------------------------- /1-lab-lambdaDynamoDB/source/lambda-functions/list-data/app.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import os 3 | import json 4 | 5 | DDB_TABLE = os.getenv("TABLE_NAME") 6 | 7 | 8 | def list_data(): 9 | data = [] 10 | dynamodb = boto3.resource('dynamodb') 11 | table = dynamodb.Table(DDB_TABLE) 12 | scan_kwargs = {} 13 | 14 | done = False 15 | start_key = None 16 | while not done: 17 | if start_key: 18 | scan_kwargs['ExclusiveStartKey'] = start_key 19 | response = table.scan(**scan_kwargs) 20 | data.extend(response.get('Items')) 21 | start_key = response.get('LastEvaluatedKey', None) 22 | done = start_key is None 23 | return data 24 | 25 | 26 | def handler(event, context): 27 | print("Starting Lambda function to add a record into DynamoDB") 28 | data = list_data() 29 | print("Random record added to DynamoDB Table") 30 | return {"statusCode": 200, "body": json.dumps({"data": data})} 31 | -------------------------------------------------------------------------------- /1-lab-lambdaDynamoDB/source/lambda-functions/store-data/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donnieprakoso/workshop-restAPI/b3287d5749b65648710dde4e736ba55b73371c6b/1-lab-lambdaDynamoDB/source/lambda-functions/store-data/.gitignore -------------------------------------------------------------------------------- /1-lab-lambdaDynamoDB/source/lambda-functions/store-data/app.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import uuid 3 | import os 4 | import datetime 5 | import json 6 | 7 | DDB_TABLE = os.getenv("TABLE_NAME") 8 | 9 | 10 | def save_to_db(data): 11 | dynamodb = boto3.resource('dynamodb') 12 | table = dynamodb.Table(DDB_TABLE) 13 | table.put_item(Item=data) 14 | 15 | 16 | def handler(event, context): 17 | print("Starting Lambda function to add a record into DynamoDB") 18 | random_string = str(uuid.uuid4()) 19 | save_to_db({ 20 | "ID":random_string, 21 | "date_created": 22 | datetime.datetime.now().strftime("%Y-%m-%d, %H:%M:%S") 23 | }) 24 | print("Random record added to DynamoDB Table") 25 | return {"statusCode": 200, "body": json.dumps({"message": "success"})} 26 | -------------------------------------------------------------------------------- /1-lab-lambdaDynamoDB/work/README.md: -------------------------------------------------------------------------------- 1 | # Work Folder 2 | Please work on the workshop activities in this folder. The purpose is for you to have a separate space to develop while still being able to see the primary reference. 3 | -------------------------------------------------------------------------------- /1-lab-lambdaDynamoDB/work/cdk/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donnieprakoso/workshop-restAPI/b3287d5749b65648710dde4e736ba55b73371c6b/1-lab-lambdaDynamoDB/work/cdk/.gitignore -------------------------------------------------------------------------------- /1-lab-lambdaDynamoDB/work/cdk/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from aws_cdk import aws_iam as _iam 4 | from aws_cdk import aws_lambda as _lambda 5 | from aws_cdk import aws_dynamodb as _ddb 6 | from aws_cdk import core 7 | 8 | 9 | class CdkStack(core.Stack): 10 | def __init__(self, scope: core.Construct, id: str, stack_prefix:str, **kwargs) -> None: 11 | super().__init__(scope, id, **kwargs) 12 | 13 | # Create DynamoDB Table 14 | ''' 15 | [TASK] Create DynamoDB Table. Use this variable name: ddb_table 16 | ''' 17 | 18 | ''' 19 | [END of TASK] 20 | ''' 21 | 22 | ## Define IAM Roles 23 | lambda_role = _iam.Role( 24 | self, 25 | id='{}-lambda-role'.format(stack_prefix), 26 | assumed_by=_iam.ServicePrincipal('lambda.amazonaws.com')) 27 | 28 | cw_policy_statement = _iam.PolicyStatement(effect=_iam.Effect.ALLOW) 29 | cw_policy_statement.add_actions("logs:CreateLogGroup") 30 | cw_policy_statement.add_actions("logs:CreateLogStream") 31 | cw_policy_statement.add_actions("logs:PutLogEvents") 32 | cw_policy_statement.add_actions("logs:DescribeLogStreams") 33 | cw_policy_statement.add_resources("*") 34 | lambda_role.add_to_policy(cw_policy_statement) 35 | 36 | # Add role for DynamoDB 37 | dynamodb_policy_statement = _iam.PolicyStatement( 38 | effect=_iam.Effect.ALLOW) 39 | ''' 40 | [TASK] Define IAM Roles for Lambda to do activities with DynamoDB 41 | ''' 42 | ''' 43 | [END of TASK] 44 | ''' 45 | ''' 46 | [TASK] Add DynamoDB Table ARN resource into the policy statement 47 | ''' 48 | ''' 49 | [END of TASK] 50 | ''' 51 | 52 | 53 | lambda_role.add_to_policy(dynamodb_policy_statement) 54 | 55 | ## AWS Lambda Functions 56 | fnLambda_storeData = _lambda.Function( 57 | self, 58 | "{}-function-storeData".format(stack_prefix), 59 | code=_lambda.AssetCode("../lambda-functions/store-data"), 60 | handler="app.handler", 61 | timeout=core.Duration.seconds(60), 62 | role=lambda_role, 63 | runtime=_lambda.Runtime.PYTHON_3_8) 64 | fnLambda_storeData.add_environment("TABLE_NAME", ddb_table.table_name) 65 | 66 | ''' 67 | [TASK] Define AWS Lambda Function for list-data. Use this variable name: fnLambda_listData 68 | ''' 69 | 70 | ''' 71 | [END of TASK] 72 | ''' 73 | 74 | ''' 75 | [TASK] Add environment_variable for DynamoDB Table name so AWS Lambda Function can use the variable 76 | ''' 77 | 78 | ''' 79 | [END of TASK] 80 | ''' 81 | 82 | 83 | core.CfnOutput(self, "{}-output-dynamodbTable".format(stack_prefix), value=ddb_table.table_name, export_name="{}-ddbTable".format(stack_prefix)) 84 | core.CfnOutput(self, "{}-output-lambdaStoreData".format(stack_prefix), value=fnLambda_storeData.function_name, export_name="{}-lambdaStoreDataName".format(stack_prefix)) 85 | core.CfnOutput(self, "{}-output-lambdaListData".format(stack_prefix), value=fnLambda_listData.function_name, export_name="{}-lambdaListDataName".format(stack_prefix)) 86 | 87 | stack_prefix='restAPI-lab1-lambdaDynamoDB' 88 | app = core.App() 89 | stack = CdkStack(app, stack_prefix, stack_prefix=stack_prefix) 90 | ''' 91 | [TASK] Tag CDK App 92 | ''' 93 | ''' 94 | [END of TASK] 95 | ''' 96 | -------------------------------------------------------------------------------- /1-lab-lambdaDynamoDB/work/cdk/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donnieprakoso/workshop-restAPI/b3287d5749b65648710dde4e736ba55b73371c6b/1-lab-lambdaDynamoDB/work/cdk/requirements.txt -------------------------------------------------------------------------------- /1-lab-lambdaDynamoDB/work/lambda-functions/list-data/app.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import os 3 | import json 4 | 5 | DDB_TABLE = os.getenv("TABLE_NAME") 6 | 7 | 8 | def list_data(): 9 | data = [] 10 | dynamodb = boto3.resource('dynamodb') 11 | table = dynamodb.Table(DDB_TABLE) 12 | scan_kwargs = {} 13 | done = False 14 | start_key = None 15 | while not done: 16 | if start_key: 17 | scan_kwargs['ExclusiveStartKey'] = start_key 18 | ''' 19 | [TASK] Get all data by using scan API and pass scan_kwargs as parameter and name the variable as 'response' 20 | All results from DynamoDB will be stored in 'Items' and you need to extend (not append) the data variable to include the 'Items' 21 | ''' 22 | ''' 23 | [END OF TASK] 24 | ''' 25 | start_key = response.get('LastEvaluatedKey', None) 26 | done = start_key is None 27 | return data 28 | 29 | 30 | def handler(event, context): 31 | print("Starting Lambda function to add a record into DynamoDB") 32 | data = list_data() 33 | print("Random record added to DynamoDB Table") 34 | ''' 35 | [TASK] Return the response in dictionary with following format: 36 | { 37 | statusCode: 200 38 | body: 39 | { 40 | data: data 41 | } 42 | } 43 | Please note that the body need to be dumped in JSON format 44 | ''' 45 | 46 | ''' 47 | [END OF TASK] 48 | ''' 49 | -------------------------------------------------------------------------------- /1-lab-lambdaDynamoDB/work/lambda-functions/store-data/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donnieprakoso/workshop-restAPI/b3287d5749b65648710dde4e736ba55b73371c6b/1-lab-lambdaDynamoDB/work/lambda-functions/store-data/.gitignore -------------------------------------------------------------------------------- /1-lab-lambdaDynamoDB/work/lambda-functions/store-data/app.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import uuid 3 | import os 4 | import datetime 5 | import json 6 | 7 | DDB_TABLE = os.getenv("TABLE_NAME") 8 | 9 | 10 | def save_to_db(data): 11 | dynamodb = boto3.resource('dynamodb') 12 | table = dynamodb.Table(DDB_TABLE) 13 | ''' 14 | [TASK] Save the data into database ONLY by using put_item API 15 | ''' 16 | 17 | ''' 18 | [END OF TASK] 19 | ''' 20 | 21 | def handler(event, context): 22 | print("Starting Lambda function to add a record into DynamoDB") 23 | random_string = str(uuid.uuid4()) 24 | save_to_db({ 25 | "ID":random_string, 26 | "date_created": 27 | datetime.datetime.now().strftime("%Y-%m-%d, %H:%M:%S") 28 | }) 29 | print("Random record added to DynamoDB Table") 30 | 31 | ''' 32 | [TASK] Return the response in dictionary with following format: 33 | { 34 | statusCode: 200 35 | body: 36 | { 37 | message: success 38 | } 39 | } 40 | Please note that the body need to be dumped in JSON format 41 | ''' 42 | 43 | ''' 44 | [END OF TASK] 45 | ''' 46 | -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/README.md: -------------------------------------------------------------------------------- 1 | # Lab 2: Implementing REST API with Amazon API Gateway 2 | This lab is the main content of this workshop where you are going to implement REST API with Amazon API Gateway, AWS Lambda and Amazon DynamoDB. You will learn how to build resources following REST API guidelines using HTTP protocol and methods. 3 | 4 | You will build 4 AWS Lambda Functions, each of them are integrated with Amazon API Gateway endpoint and respective HTTP method. There are few small details in this lab that you'll find useful while building your API, for examples: defining resources and how to get URL parameters. 5 | 6 | ## Diagram 7 | ![Lab 2 Diagram](https://raw.githubusercontent.com/donnieprakoso/workshop-restAPI/main/2-lab-apiGatewayIntegration/Workshop-REST%20API-Page-2.png) 8 | 9 | ## Tasks 10 | These are the tasks that you need to complete. If at some point you are stuck, please refer to the primary reference located at `source/` folder. 11 | 12 | ### Step 0: Prepare work folder and boto3 13 | #### Install boto3 library 14 | - Open your terminal 15 | - Run this command 16 | ```bash 17 | pip install boto3 18 | ``` 19 | 20 | #### Preparing work folder 21 | - Navigate to `work/` folder 22 | - You will find 2 sub-directories named `cdk` and `lambda-functions` 23 | 24 | ### Step 1: Structuring AWS Lambda Functions work folders 25 | - Navigate to `work/lambda-functions/` folder 26 | - You will find 5 sub-directories in this folder: 27 | 1. `save-data` 28 | 2. `list-data` 29 | 3. `get-data` 30 | 4. `delete-data` 31 | 32 | ### Step 2: Working on `save-data` 33 | - Navigate to `work/lambda-functions/save-data` 34 | - Open `app.py` 35 | 36 | ### Step 3: Get request payload 37 | - Get the request payload from `event` variable and use `data` as name of the variable. 38 | 39 | >**💡 HINT** 40 | - Amazon API Gateway invokes your function synchronously with an event. This event contains JSON representation of the HTTP request. You can refer to following link, to see the example of the structure. [Link](https://github.com/awsdocs/aws-lambda-developer-guide/blob/main/sample-apps/nodejs-apig/event.json) 41 | - Request payload is stored in `body` 42 | - As the request payload in `event` is in JSON format, you'll need to load the JSON string. [Link](https://docs.python.org/3/library/json.html#json.loads) 43 | 44 | > ### 😕 Are you stuck? 45 | > See the solution [here](https://github.com/donnieprakoso/workshop-restAPI/blob/main/2-lab-apiGatewayIntegration/source/lambda-functions/save-data/app.py) 46 | 47 | ### Step 4: Working on `list-data` 48 | - Navigate to `work/lambda-functions/list-data` 49 | - Open `app.py` 50 | 51 | ### Step 5: Specify the attributes 52 | - One way to list all data, you will need to use `scan` API. In this case, you need to specify to return only field "ID" to reduce the size of response returned to client. 53 | 54 | >**💡 HINT** 55 | - Use `ProjectionExpression` parameter to return only specific attributes. [Link](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#DynamoDB.Client.scan) 56 | 57 | > ### 😕 Are you stuck? 58 | > See the solution [here](https://github.com/donnieprakoso/workshop-restAPI/blob/main/2-lab-apiGatewayIntegration/source/lambda-functions/list-data/app.py) 59 | 60 | ### Step 6: Working on `get-data` 61 | - Navigate to `work/lambda-functions/get-data` 62 | - Open `app.py` 63 | 64 | ### Step 7: Get specific record from DynamoDB 65 | - In the `get_data` function, you need to get a record based on its ID. Use the `id` parameter in your DynamoDB call. 66 | 67 | >**💡 HINT** 68 | - Use `get_item()` API to return an item with the given primary key. [Link](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#DynamoDB.Client.get_item) 69 | 70 | > ### 😕 Are you stuck? 71 | > See the solution [here](https://github.com/donnieprakoso/workshop-restAPI/blob/main/2-lab-apiGatewayIntegration/source/lambda-functions/get-data/app.py) 72 | 73 | ### Step 8: Load path parameters from `event` variable 74 | - To get the ID from the request, you need to load the path parameters of the request. Use `id` as the variable name. 75 | 76 | >**💡 HINT** 77 | - Similar to the request payload, all path parameters are also stored in the `event`. Refer to the following link to figure out which variables holds the `pathParameters` . [Link](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#DynamoDB.Client.get_item) 78 | 79 | > ### 😕 Are you stuck? 80 | > See the solution [here](https://github.com/donnieprakoso/workshop-restAPI/blob/main/2-lab-apiGatewayIntegration/source/lambda-functions/get-data/app.py) 81 | 82 | ### Step 9: Working on `delete-data` 83 | **You don't need to do anything.** This is already been provided in the `work/lambda-functions/delete-data/app.py` file as a complete code. 84 | 85 | ### Step 10: Setting cdk.json 86 | - Navigate to `work/cdk/` 87 | - Create a file named `cdk.json` 88 | - Open `cdk.json` and write these lines. These lines are to give a set of instruction for AWS CDK on how to build this app. 89 | ```json 90 | { 91 | "app":"python3 app.py", 92 | "context":{} 93 | } 94 | ``` 95 | - Open `app.py`. In this file, you see that we are missing few things to make this as a complete code. Read the file thoroughly and go to the next steps below. 96 | 97 | ### Step 12: Create REST API Object 98 | - Before you can integrate Lambda functions and Amazon API Gateway, you need to create a REST API object. 99 | 100 | >**💡 HINT** 101 | >- Use `RestApi` construct. Here's the [API reference](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_apigateway/RestApi.html#restapi). 102 | 103 | > ### 😕 Are you stuck? 104 | > See the solution [here](https://github.com/donnieprakoso/workshop-restAPI/blob/main/2-lab-apiGatewayIntegration/source/cdk/app.py) 105 | 106 | ### Step 13: Create integrations for each Lambda Functions 107 | - To integrate Lambda functions and Amazon API Gateway, you'll need to create integration using `LambdaIntegration` for each Lambda functions. 108 | - By the end of this task, you should have 4 `LambdaIntegration` objects. 109 | 110 | 111 | >**💡 HINT** 112 | >- Here's the [API reference](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_apigateway/LambdaIntegration.html). 113 | 114 | > ### 😕 Are you stuck? 115 | > See the solution [here](https://github.com/donnieprakoso/workshop-restAPI/blob/main/2-lab-apiGatewayIntegration/source/cdk/app.py) 116 | 117 | ### Step 14: Create REST resource 118 | - At this point, you have a `RestApi` object and 4 `LambdaIntegration` objects. Now you need to create a REST resource. 119 | - There are two resources that you need to create: `data` and `data/item_id`. The example of endpoint for our API is as follow: `http://example.com/data` and `http://example.com/data/item_id` 120 | - For this task, you need to create `data` resource. 121 | 122 | >**💡 HINT** 123 | >- To add resources for your `RestApi` object, you need to access the `root` as it represents the root resource (`/`) of your API endpoint. 124 | >- Here's the reference to `root` base. [Link](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_apigateway/RestApiBase.html#aws_cdk.aws_apigateway.RestApiBase.root) 125 | >- ...which will lead you to `IResource` class where you can do operations. [Link](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_apigateway/IResource.html#aws_cdk.aws_apigateway.IResource) 126 | >- Use following API link to add resources. [Link](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_apigateway/IResource.html#aws_cdk.aws_apigateway.IResource.add_resource) 127 | 128 | > ### 😕 Are you stuck? 129 | > See the solution [here](https://github.com/donnieprakoso/workshop-restAPI/blob/main/2-lab-apiGatewayIntegration/source/cdk/app.py) 130 | 131 | ### Step 14: Create HTTP methods for `/data` resource 132 | - Once you have defined the `/data` resource, now you need to define what the HTTP methods available for this resource. 133 | - There are 2 methods you need to define for this resource: 1) POST method for `save-data` and 2) GET method for `list-data` 134 | 135 | >**💡 HINT** 136 | >- Use following API link to add method. [Link](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_apigateway/IResource.html#aws_cdk.aws_apigateway.IResource.add_method) 137 | >- Use the Lambda integration objects that you've created earlier as the `target` parameter for `add_method()`. 138 | 139 | > ### 😕 Are you stuck? 140 | > See the solution [here](https://github.com/donnieprakoso/workshop-restAPI/blob/main/2-lab-apiGatewayIntegration/source/cdk/app.py) 141 | 142 | ### Step 15: Add path parameter `id` within `data` resource 143 | - To get data details and to delete a data, you'll need to pass the ID. This is the endpoint example: `http://example.com/data/96bbcaff-6546-4e85-b246-9f61fa94c173`. 144 | - To assign a path parameter, use `{VARIABLE_NAME}` while calling `add_resource()` on the `data` resource object. 145 | 146 | > ### 😕 Are you stuck? 147 | > See the solution [here](https://github.com/donnieprakoso/workshop-restAPI/blob/main/2-lab-apiGatewayIntegration/source/cdk/app.py) 148 | 149 | ### Step 16: Create HTTP methods for `/data` resource 150 | - There are 2 methods you need to define for this resource: 1) GET method for `get-data` and 2) DELETE method for `delete-data` 151 | 152 | > ### 😕 Are you stuck? 153 | > See the solution [here](https://github.com/donnieprakoso/workshop-restAPI/blob/main/2-lab-apiGatewayIntegration/source/cdk/app.py) 154 | 155 | ### Step 17: Install all required libraries to build and run CDK app 156 | - Open your terminal 157 | - Navigate to `work/cdk/` 158 | - Create a file named `requirements.txt`. This is a standard method to install any dependencies for Python applications. 159 | - Add these lines: 160 | ``` 161 | aws-cdk.core==1.96.0 162 | aws-cdk.aws-iam==1.96.0 163 | aws-cdk.aws-lambda==1.96.0 164 | aws-cdk.aws-apigateway==1.96.0 165 | aws-cdk.aws-dynamodb==1.96.0 166 | ``` 167 | - Install the libraries by executing: 168 | ```bash 169 | pip install -r requirements.txt 170 | ``` 171 | ### Step 18: Deploy 172 | - Open your terminal 173 | - Navigate to `work/cdk/` 174 | - Deploy the app by executing: 175 | ```bash 176 | cdk deploy 177 | ``` 178 | - When you successfully deployed the CDK APP, you will get an API endpoint running on Amazon API Gateway. **Note down this URL.** 179 | 180 | #### ⚠️ Time for Testing ⚠️ 181 | - Congrats! At this point, you have a REST API running on Amazon API Gateway and integrated with AWS Lambda. 182 | - For testing, we're going to do 4 scenarios: 183 | 1. Add a couple of data using `save-data` API 184 | 2. List data using `list-data` API 185 | 3. Get a data using `get-data` API 186 | 4. Delete a data using `delete-data` API 187 | - There are few ways on doing this, and for you who have cURL installed, I've prepared a simple `bash` script that you can execute. 188 | - Before you execute the bash script, you need to change the API endpoint to your endpoint. 189 | - If you don't have cURL installed, refer to this [page](https://curl.se/download.html) to learn how to install. 190 | 191 | ### Step 19: Add couple of data 192 | **⚠️ Before you execute the bash script, you need to change the API endpoint to your endpoint** 193 | 194 | - Open your terminal and navigate to `work/testing-api/` 195 | - Run `save-data.sh` more than 1 time to add data. 196 | - Let's check DynamoDB table. Go to [DynamoDB dashboard](https://ap-southeast-1.console.aws.amazon.com/dynamodb/home?region=ap-southeast-1#tables:) and filter the tables with `restAPI` or you can get the name of the table from the `deploy` output. 197 | - Open the table. 198 | - If you see couple of records in your table, then your API works. 199 | 200 | ### Step 20: List all data 201 | **⚠️ Before you execute the bash script, you need to change the API endpoint to your endpoint** 202 | 203 | - Run `list-data.sh` to get all data 204 | - You will get response from the API all data with their IDs. 205 | - You won't see other fields as we set `ProjectionExpression` to only select `ID` field. 206 | - Note one of the IDs for next step. 207 | 208 | ### Step 21: Get details of a data 209 | **⚠️ Before you execute the bash script, you need to change the API endpoint to your endpoint** 210 | 211 | - Run `get-data.sh` and enter the data ID 212 | - You'll get the full data details. 213 | 214 | ### Step 22: Delete a data 215 | **⚠️ Before you execute the bash script, you need to change the API endpoint to your endpoint** 216 | 217 | - Run `delete-data.sh` and enter the data ID 218 | - Run `list-data.sh` to get all data 219 | - Check if the data with the `ID` you specified in this step has been deleted. 220 | 221 | # 🤘🏻Congrats! 222 | You've just finished the Lab 2. 223 | 224 | ## Cleaning Up 225 | To clean up all resources, follow these instructions below: 226 | 1. Go to `work/cdk/` 227 | 2. Run `cdk destroy` command 228 | ```bash 229 | cdk destroy 230 | ``` -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/Workshop-REST API-Page-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donnieprakoso/workshop-restAPI/b3287d5749b65648710dde4e736ba55b73371c6b/2-lab-apiGatewayIntegration/Workshop-REST API-Page-2.png -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/source/cdk/.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | package-lock.json 3 | __pycache__ 4 | .pytest_cache 5 | .env 6 | *.egg-info 7 | 8 | # CDK asset staging directory 9 | .cdk.staging 10 | cdk.out 11 | -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/source/cdk/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from aws_cdk import aws_iam as _iam 4 | from aws_cdk import aws_lambda as _lambda 5 | from aws_cdk import aws_dynamodb as _ddb 6 | from aws_cdk import aws_apigateway as _ag 7 | from aws_cdk import core 8 | 9 | 10 | class CdkStack(core.Stack): 11 | def __init__(self, scope: core.Construct, id: str, stack_prefix: str, **kwargs) -> None: 12 | super().__init__(scope, id, **kwargs) 13 | 14 | # Model all required resources 15 | ddb_table = _ddb.Table( 16 | self, 17 | id='{}-data'.format(stack_prefix), 18 | table_name='{}-data'.format(stack_prefix), 19 | partition_key=_ddb.Attribute(name='ID', 20 | type=_ddb.AttributeType.STRING), 21 | removal_policy=core.RemovalPolicy.DESTROY,# THIS IS NOT RECOMMENDED FOR PRODUCTION USE 22 | read_capacity=1, 23 | write_capacity=1) 24 | 25 | # IAM Roles 26 | lambda_role = _iam.Role( 27 | self, 28 | id='{}-lambda-role'.format(stack_prefix), 29 | assumed_by=_iam.ServicePrincipal('lambda.amazonaws.com')) 30 | 31 | cw_policy_statement = _iam.PolicyStatement(effect=_iam.Effect.ALLOW) 32 | cw_policy_statement.add_actions("logs:CreateLogGroup") 33 | cw_policy_statement.add_actions("logs:CreateLogStream") 34 | cw_policy_statement.add_actions("logs:PutLogEvents") 35 | cw_policy_statement.add_actions("logs:DescribeLogStreams") 36 | cw_policy_statement.add_resources("*") 37 | lambda_role.add_to_policy(cw_policy_statement) 38 | 39 | # Add role for DynamoDB 40 | dynamodb_policy_statement = _iam.PolicyStatement( 41 | effect=_iam.Effect.ALLOW) 42 | dynamodb_policy_statement.add_actions("dynamodb:PutItem") 43 | dynamodb_policy_statement.add_actions("dynamodb:GetItem") 44 | dynamodb_policy_statement.add_actions("dynamodb:UpdateItem") 45 | dynamodb_policy_statement.add_actions("dynamodb:DeleteItem") 46 | dynamodb_policy_statement.add_actions("dynamodb:Scan") 47 | dynamodb_policy_statement.add_actions("dynamodb:Query") 48 | dynamodb_policy_statement.add_actions("dynamodb:ConditionCheckItem") 49 | dynamodb_policy_statement.add_resources(ddb_table.table_arn) 50 | lambda_role.add_to_policy(dynamodb_policy_statement) 51 | 52 | # AWS Lambda Functions 53 | fnLambda_saveData = _lambda.Function( 54 | self, 55 | "{}-function-saveData".format(stack_prefix), 56 | code=_lambda.AssetCode("../lambda-functions/save-data"), 57 | handler="app.handler", 58 | timeout=core.Duration.seconds(60), 59 | role=lambda_role, 60 | runtime=_lambda.Runtime.PYTHON_3_8) 61 | fnLambda_saveData.add_environment("TABLE_NAME", ddb_table.table_name) 62 | 63 | fnLambda_listData = _lambda.Function( 64 | self, 65 | "{}-function-listData".format(stack_prefix), 66 | code=_lambda.AssetCode("../lambda-functions/list-data"), 67 | handler="app.handler", 68 | timeout=core.Duration.seconds(60), 69 | role=lambda_role, 70 | runtime=_lambda.Runtime.PYTHON_3_8) 71 | fnLambda_listData.add_environment("TABLE_NAME", ddb_table.table_name) 72 | 73 | fnLambda_getData = _lambda.Function( 74 | self, 75 | "{}-function-getData".format(stack_prefix), 76 | code=_lambda.AssetCode("../lambda-functions/get-data"), 77 | handler="app.handler", 78 | timeout=core.Duration.seconds(60), 79 | role=lambda_role, 80 | runtime=_lambda.Runtime.PYTHON_3_8) 81 | fnLambda_getData.add_environment("TABLE_NAME", ddb_table.table_name) 82 | 83 | fnLambda_deleteData = _lambda.Function( 84 | self, 85 | "{}-function-deleteData".format(stack_prefix), 86 | code=_lambda.AssetCode("../lambda-functions/delete-data"), 87 | handler="app.handler", 88 | timeout=core.Duration.seconds(60), 89 | role=lambda_role, 90 | runtime=_lambda.Runtime.PYTHON_3_8) 91 | fnLambda_deleteData.add_environment("TABLE_NAME", ddb_table.table_name) 92 | 93 | api = _ag.RestApi( 94 | self, 95 | id="{}-api-gateway".format(stack_prefix), 96 | ) 97 | 98 | int_saveData = _ag.LambdaIntegration(fnLambda_saveData) 99 | int_listData = _ag.LambdaIntegration(fnLambda_listData) 100 | int_getData = _ag.LambdaIntegration(fnLambda_getData) 101 | int_deleteData = _ag.LambdaIntegration(fnLambda_deleteData) 102 | 103 | res_data = api.root.add_resource('data') 104 | res_data.add_method('POST',int_saveData) 105 | res_data.add_method('GET',int_listData) 106 | 107 | res_data_id = res_data.add_resource('{id}') 108 | res_data_id.add_method('GET',int_getData) 109 | res_data_id.add_method('DELETE',int_deleteData) 110 | 111 | core.CfnOutput(self, "{}-output-dynamodbTable".format(stack_prefix), 112 | value=ddb_table.table_name, export_name="{}-ddbTable".format(stack_prefix)) 113 | core.CfnOutput(self, "{}-output-apiEndpointURL".format(stack_prefix), 114 | value=api.url, export_name="{}-apiEndpointURL".format(stack_prefix)) 115 | 116 | 117 | stack_prefix = 'restAPI-lab2-apiGatewayIntegration' 118 | app = core.App() 119 | stack = CdkStack(app, stack_prefix, stack_prefix=stack_prefix) 120 | core.Tags.of(stack).add('Name', stack_prefix) 121 | 122 | app.synth() 123 | -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/source/cdk/cdk.context.json: -------------------------------------------------------------------------------- 1 | { 2 | "@aws-cdk/core:enableStackNameDuplicates": "true", 3 | "aws-cdk:enableDiffNoFail": "true" 4 | } 5 | -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/source/cdk/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "python3 app.py" 3 | } 4 | -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/source/cdk/requirements.txt: -------------------------------------------------------------------------------- 1 | aws-cdk.core==1.96.0 2 | aws-cdk.aws-iam==1.96.0 3 | aws-cdk.aws-lambda==1.96.0 4 | aws-cdk.aws-apigateway==1.96.0 5 | aws-cdk.aws-dynamodb==1.96.0 -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/source/lambda-functions/delete-data/app.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import os 3 | import json 4 | from datetime import datetime 5 | import logging 6 | import json 7 | 8 | ''' 9 | AWS Lambda Function to delete data. Requests come from Amazon API Gateway. 10 | ''' 11 | 12 | logger = logging.getLogger() 13 | logger.setLevel(logging.INFO) 14 | 15 | 16 | def delete_from_db(id): 17 | dynamodb = boto3.resource('dynamodb') 18 | table = dynamodb.Table(os.getenv("TABLE_NAME")) 19 | table.delete_item( 20 | Key={ 21 | 'ID': id 22 | } 23 | ) 24 | 25 | 26 | def handler(event, context): 27 | logger.info(event) 28 | logger.info('delete-data is called') 29 | id = event['pathParameters']['id'] if 'id' in event['pathParameters'] else None 30 | if id: 31 | delete_from_db(id) 32 | response = {"statusCode": 200, "body": json.dumps({"message": "success"})} 33 | return response -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/source/lambda-functions/get-data/app.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import os 3 | import json 4 | from datetime import datetime 5 | import logging 6 | import json 7 | 8 | ''' 9 | AWS Lambda Function to get all data. Requests come from Amazon API Gateway. 10 | ''' 11 | 12 | logger = logging.getLogger() 13 | logger.setLevel(logging.INFO) 14 | 15 | 16 | def get_data(id): 17 | dynamodb = boto3.resource('dynamodb') 18 | table = dynamodb.Table(os.getenv("TABLE_NAME")) 19 | response = table.get_item( 20 | Key={ 21 | 'ID': id 22 | } 23 | ) 24 | item = response['Item'] 25 | return item 26 | 27 | 28 | def handler(event, context): 29 | logger.info(event) 30 | logger.info('get-data is called') 31 | id = event['pathParameters']['id'] if 'id' in event['pathParameters'] else None 32 | if id: 33 | data = get_data(id) 34 | response = {"statusCode": 200, "body": json.dumps({"data": data})} 35 | return response -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/source/lambda-functions/list-data/app.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import os 3 | import json 4 | from datetime import datetime 5 | import logging 6 | import json 7 | 8 | ''' 9 | AWS Lambda Function to list all data. Requests come from Amazon API Gateway. 10 | ''' 11 | 12 | logger = logging.getLogger() 13 | logger.setLevel(logging.INFO) 14 | 15 | 16 | def list_data(): 17 | data = [] 18 | dynamodb = boto3.resource('dynamodb') 19 | table = dynamodb.Table(os.getenv("TABLE_NAME")) 20 | scan_kwargs = {} 21 | scan_kwargs["ProjectionExpression"]="ID" 22 | 23 | done = False 24 | start_key = None 25 | while not done: 26 | if start_key: 27 | scan_kwargs['ExclusiveStartKey'] = start_key 28 | response = table.scan(**scan_kwargs) 29 | data.extend(response.get('Items')) 30 | start_key = response.get('LastEvaluatedKey', None) 31 | done = start_key is None 32 | return data 33 | 34 | 35 | def handler(event, context): 36 | logger.info(event) 37 | logger.info('list-data is called') 38 | data = list_data() 39 | response = {"statusCode": 200, "body": json.dumps({"data": data})} 40 | return response -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/source/lambda-functions/save-data/app.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import os 3 | import json 4 | from datetime import datetime 5 | import logging 6 | import json 7 | import uuid 8 | 9 | ''' 10 | AWS Lambda Function to store data. Requests come from Amazon API Gateway. 11 | ''' 12 | 13 | logger = logging.getLogger() 14 | logger.setLevel(logging.INFO) 15 | 16 | def save_to_db(data): 17 | dynamodb = boto3.resource('dynamodb') 18 | table = dynamodb.Table(os.getenv("TABLE_NAME")) 19 | response = table.update_item( 20 | Key={'ID': data['ID']}, 21 | UpdateExpression="set last_updated=:sts, text_data=:text_data", 22 | ExpressionAttributeValues={ 23 | ':sts': datetime.now().strftime("%m-%d-%Y %H:%M:%S"), 24 | ':text_data': data['text'] 25 | }) 26 | 27 | 28 | def handler(event, context): 29 | logger.info(event) 30 | logger.info('store-data is called') 31 | data = json.loads(event['body']) 32 | data['ID'] = str(uuid.uuid4()) if "ID" not in data else data['ID'] 33 | save_to_db(data) 34 | response = {"statusCode": 200, "body": json.dumps({"message": "success"})} 35 | return response -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/source/testing-api/delete-data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo -n "What is the data ID (you can get it by executing list-data API)?" 3 | read id 4 | curl -vX DELETE https://your-api-endpoint/prod/data/$id -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/source/testing-api/get-data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo -n "What is the data ID (you can get it by executing list-data API)?" 3 | read id 4 | curl -vX GET https://your-api-endpoint/prod/data/$id -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/source/testing-api/list-data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | curl -vX GET https://your-api-endpoint/prod/data -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/source/testing-api/save-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "text":"Hello!" 3 | } -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/source/testing-api/save-data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | curl -vX POST https://your-api-endpoint/prod/data -d @save-data.json --header "Content-Type: application/json" -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/work/README.md: -------------------------------------------------------------------------------- 1 | # Work Folder 2 | Please work on the workshop activities in this folder. The purpose is for you to have a separate space to develop while still being able to see the primary reference. 3 | -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/work/cdk/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from aws_cdk import aws_iam as _iam 4 | from aws_cdk import aws_lambda as _lambda 5 | from aws_cdk import aws_dynamodb as _ddb 6 | from aws_cdk import aws_apigateway as _ag 7 | from aws_cdk import core 8 | 9 | 10 | class CdkStack(core.Stack): 11 | def __init__(self, scope: core.Construct, id: str, stack_prefix: str, **kwargs) -> None: 12 | super().__init__(scope, id, **kwargs) 13 | 14 | # Model all required resources 15 | ddb_table = _ddb.Table( 16 | self, 17 | id='{}-data'.format(stack_prefix), 18 | table_name='{}-data'.format(stack_prefix), 19 | partition_key=_ddb.Attribute(name='ID', 20 | type=_ddb.AttributeType.STRING), 21 | removal_policy=core.RemovalPolicy.DESTROY,# THIS IS NOT RECOMMENDED FOR PRODUCTION USE 22 | read_capacity=1, 23 | write_capacity=1) 24 | 25 | # IAM Roles 26 | lambda_role = _iam.Role( 27 | self, 28 | id='{}-lambda-role'.format(stack_prefix), 29 | assumed_by=_iam.ServicePrincipal('lambda.amazonaws.com')) 30 | 31 | cw_policy_statement = _iam.PolicyStatement(effect=_iam.Effect.ALLOW) 32 | cw_policy_statement.add_actions("logs:CreateLogGroup") 33 | cw_policy_statement.add_actions("logs:CreateLogStream") 34 | cw_policy_statement.add_actions("logs:PutLogEvents") 35 | cw_policy_statement.add_actions("logs:DescribeLogStreams") 36 | cw_policy_statement.add_resources("*") 37 | lambda_role.add_to_policy(cw_policy_statement) 38 | 39 | # Add role for DynamoDB 40 | dynamodb_policy_statement = _iam.PolicyStatement( 41 | effect=_iam.Effect.ALLOW) 42 | dynamodb_policy_statement.add_actions("dynamodb:PutItem") 43 | dynamodb_policy_statement.add_actions("dynamodb:GetItem") 44 | dynamodb_policy_statement.add_actions("dynamodb:UpdateItem") 45 | dynamodb_policy_statement.add_actions("dynamodb:DeleteItem") 46 | dynamodb_policy_statement.add_actions("dynamodb:Scan") 47 | dynamodb_policy_statement.add_actions("dynamodb:Query") 48 | dynamodb_policy_statement.add_actions("dynamodb:ConditionCheckItem") 49 | dynamodb_policy_statement.add_resources(ddb_table.table_arn) 50 | lambda_role.add_to_policy(dynamodb_policy_statement) 51 | 52 | # AWS Lambda Functions 53 | fnLambda_saveData = _lambda.Function( 54 | self, 55 | "{}-function-saveData".format(stack_prefix), 56 | code=_lambda.AssetCode("../lambda-functions/save-data"), 57 | handler="app.handler", 58 | timeout=core.Duration.seconds(60), 59 | role=lambda_role, 60 | runtime=_lambda.Runtime.PYTHON_3_8) 61 | fnLambda_saveData.add_environment("TABLE_NAME", ddb_table.table_name) 62 | 63 | fnLambda_listData = _lambda.Function( 64 | self, 65 | "{}-function-listData".format(stack_prefix), 66 | code=_lambda.AssetCode("../lambda-functions/list-data"), 67 | handler="app.handler", 68 | timeout=core.Duration.seconds(60), 69 | role=lambda_role, 70 | runtime=_lambda.Runtime.PYTHON_3_8) 71 | fnLambda_listData.add_environment("TABLE_NAME", ddb_table.table_name) 72 | 73 | fnLambda_getData = _lambda.Function( 74 | self, 75 | "{}-function-getData".format(stack_prefix), 76 | code=_lambda.AssetCode("../lambda-functions/get-data"), 77 | handler="app.handler", 78 | timeout=core.Duration.seconds(60), 79 | role=lambda_role, 80 | runtime=_lambda.Runtime.PYTHON_3_8) 81 | fnLambda_getData.add_environment("TABLE_NAME", ddb_table.table_name) 82 | 83 | fnLambda_deleteData = _lambda.Function( 84 | self, 85 | "{}-function-deleteData".format(stack_prefix), 86 | code=_lambda.AssetCode("../lambda-functions/delete-data"), 87 | handler="app.handler", 88 | timeout=core.Duration.seconds(60), 89 | role=lambda_role, 90 | runtime=_lambda.Runtime.PYTHON_3_8) 91 | fnLambda_deleteData.add_environment("TABLE_NAME", ddb_table.table_name) 92 | 93 | ''' 94 | [TASK] Create REST API object using Amazon API Gateway. 95 | Use `api` as the variable name 96 | ''' 97 | 98 | ''' 99 | [END of TASK] 100 | ''' 101 | 102 | ''' 103 | [TASK] Create integration for each Lambda Functions 104 | ''' 105 | 106 | ''' 107 | [END of TASK] 108 | ''' 109 | 110 | ''' 111 | [TASK] Create REST resource by using add_resource() 112 | ''' 113 | 114 | ''' 115 | [END of TASK] 116 | ''' 117 | 118 | ''' 119 | [TASK] Add method POST for save-data and GET for list-data functions 120 | ''' 121 | 122 | ''' 123 | [END of TASK] 124 | ''' 125 | 126 | ''' 127 | [TASK] Add path parameter `id` within `data` resource. 128 | ''' 129 | 130 | ''' 131 | [END of TASK] 132 | ''' 133 | 134 | ''' 135 | [TASK] Add method DELETE for delete-data and GET for get-data functions 136 | ''' 137 | 138 | ''' 139 | [END of TASK] 140 | ''' 141 | 142 | core.CfnOutput(self, "{}-output-dynamodbTable".format(stack_prefix), 143 | value=ddb_table.table_name, export_name="{}-ddbTable".format(stack_prefix)) 144 | core.CfnOutput(self, "{}-output-apiEndpointURL".format(stack_prefix), 145 | value=api.url, export_name="{}-apiEndpointURL".format(stack_prefix)) 146 | 147 | 148 | stack_prefix = 'restAPI-lab2-apiGatewayIntegration' 149 | app = core.App() 150 | stack = CdkStack(app, stack_prefix, stack_prefix=stack_prefix) 151 | core.Tags.of(stack).add('Name', stack_prefix) 152 | 153 | app.synth() 154 | -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/work/cdk/requirements.txt: -------------------------------------------------------------------------------- 1 | aws-cdk.core==1.96.0 2 | aws-cdk.aws-iam==1.96.0 3 | aws-cdk.aws-lambda==1.96.0 4 | aws-cdk.aws-apigateway==1.96.0 5 | aws-cdk.aws-dynamodb==1.96.0 -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/work/lambda-functions/delete-data/app.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import os 3 | import json 4 | from datetime import datetime 5 | import logging 6 | import json 7 | 8 | ''' 9 | AWS Lambda Function to delete data. Requests come from Amazon API Gateway. 10 | ''' 11 | 12 | logger = logging.getLogger() 13 | logger.setLevel(logging.INFO) 14 | 15 | 16 | def delete_from_db(id): 17 | dynamodb = boto3.resource('dynamodb') 18 | table = dynamodb.Table(os.getenv("TABLE_NAME")) 19 | table.delete_item( 20 | Key={ 21 | 'ID': id 22 | } 23 | ) 24 | 25 | 26 | def handler(event, context): 27 | logger.info(event) 28 | logger.info('delete-data is called') 29 | id = event['pathParameters']['id'] if 'id' in event['pathParameters'] else None 30 | if id: 31 | delete_from_db(id) 32 | response = {"statusCode": 200, "body": json.dumps({"message": "success"})} 33 | return response -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/work/lambda-functions/get-data/app.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import os 3 | import json 4 | from datetime import datetime 5 | import logging 6 | import json 7 | 8 | ''' 9 | AWS Lambda Function to get all data. Requests come from Amazon API Gateway. 10 | ''' 11 | 12 | logger = logging.getLogger() 13 | logger.setLevel(logging.INFO) 14 | 15 | 16 | def get_data(id): 17 | dynamodb = boto3.resource('dynamodb') 18 | table = dynamodb.Table(os.getenv("TABLE_NAME")) 19 | ''' 20 | [TASK] Get the item based on the ID from DynamoDB 21 | ''' 22 | 23 | ''' 24 | [END of TASK] 25 | ''' 26 | item = response['Item'] 27 | return item 28 | 29 | 30 | def handler(event, context): 31 | logger.info(event) 32 | logger.info('get-data is called') 33 | ''' 34 | [TASK] Load the path parameters from the `event` variable. 35 | ''' 36 | ''' 37 | [END of TASK] 38 | ''' 39 | if id: 40 | data = get_data(id) 41 | response = {"statusCode": 200, "body": json.dumps({"data": data})} 42 | return response -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/work/lambda-functions/list-data/app.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import os 3 | import json 4 | from datetime import datetime 5 | import logging 6 | import json 7 | 8 | ''' 9 | AWS Lambda Function to list all data. Requests come from Amazon API Gateway. 10 | ''' 11 | 12 | logger = logging.getLogger() 13 | logger.setLevel(logging.INFO) 14 | 15 | 16 | def list_data(): 17 | data = [] 18 | dynamodb = boto3.resource('dynamodb') 19 | table = dynamodb.Table(os.getenv("TABLE_NAME")) 20 | scan_kwargs = {} 21 | ''' 22 | [TASK] Specify the attributes only to return `ID` field by using `ProjectionExpression`. 23 | ''' 24 | 25 | ''' 26 | [END of TASK] 27 | ''' 28 | 29 | done = False 30 | start_key = None 31 | while not done: 32 | if start_key: 33 | scan_kwargs['ExclusiveStartKey'] = start_key 34 | response = table.scan(**scan_kwargs) 35 | data.extend(response.get('Items')) 36 | start_key = response.get('LastEvaluatedKey', None) 37 | done = start_key is None 38 | return data 39 | 40 | 41 | def handler(event, context): 42 | logger.info(event) 43 | logger.info('list-data is called') 44 | data = list_data() 45 | response = {"statusCode": 200, "body": json.dumps({"data": data})} 46 | return response -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/work/lambda-functions/save-data/app.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import os 3 | import json 4 | from datetime import datetime 5 | import logging 6 | import json 7 | import uuid 8 | 9 | ''' 10 | AWS Lambda Function to store data. Requests come from Amazon API Gateway. 11 | ''' 12 | 13 | logger = logging.getLogger() 14 | logger.setLevel(logging.INFO) 15 | 16 | def save_to_db(data): 17 | dynamodb = boto3.resource('dynamodb') 18 | table = dynamodb.Table(os.getenv("TABLE_NAME")) 19 | response = table.update_item( 20 | Key={'ID': data['ID']}, 21 | UpdateExpression="set last_updated=:sts, text_data=:text_data", 22 | ExpressionAttributeValues={ 23 | ':sts': datetime.now().strftime("%m-%d-%Y %H:%M:%S"), 24 | ':text_data': data['text'] 25 | }) 26 | 27 | 28 | def handler(event, context): 29 | logger.info(event) 30 | logger.info('store-data is called') 31 | 32 | ''' 33 | [TASK] Load the request payload from the `event` variable. 34 | Use `data` as the variable name. 35 | ''' 36 | 37 | ''' 38 | [END of TASK] 39 | ''' 40 | 41 | data['ID'] = str(uuid.uuid4()) if "ID" not in data else data['ID'] 42 | save_to_db(data) 43 | response = {"statusCode": 200, "body": json.dumps({"message": "success"})} 44 | return response -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/work/testing-api/delete-data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo -n "What is the data ID (you can get it by executing list-data API)?" 3 | read id 4 | curl -vX DELETE https://your-api-endpoint/prod/data/$id -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/work/testing-api/get-data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo -n "What is the data ID (you can get it by executing list-data API)?" 3 | read id 4 | curl -vX GET https://your-api-endpoint/prod/data/$id -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/work/testing-api/list-data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | curl -vX GET https://your-api-endpoint/prod/data -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/work/testing-api/save-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "text":"Hello!" 3 | } -------------------------------------------------------------------------------- /2-lab-apiGatewayIntegration/work/testing-api/save-data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | curl -vX POST https://your-api-endpoint/prod/data -d @save-data.json --header "Content-Type: application/json" -------------------------------------------------------------------------------- /3-lab-controlAccessAPI/README.md: -------------------------------------------------------------------------------- 1 | # Lab 3: Defining Access Control for REST API Using Lambda Authorizer 2 | A Lambda authorizer uses a Lambda function to control access to API in Amazon API Gateway. This particular feature is useful if you want to implement a custom authorization scheme that uses a bearer token authorization strategy. 3 | 4 | There are two types of Lambda authorizers, 1) Token Authorizer and 2) Request Authorizer. Token Authorizer uses bearer token in form of JWT or OAuth token and the Request Authorizer uses combination of headers, query string parameters, stageVariables, and $context variables. 5 | 6 | In this lab, you will build a basic API that needs to be secured by Lambda Authorizer using token. The token takes form of JWT. Please be noted that in this lab we will not cover authentication and authorization platform. 7 | 8 | You will learn how to use Lambda Authorizer and how to integrate with Amazon API Gateway using AWS CDK, to control the access of your API. 9 | 10 | ## Diagram 11 | ![Lab 3 Diagram](https://github.com/donnieprakoso/workshop-restAPI/blob/main/3-lab-controlAccessAPI/lab3-diagram.png) 12 | 13 | ## Tasks 14 | These are the tasks that you need to complete. If at some point you are stuck, please refer to the primary reference located at `source/` folder. 15 | 16 | ### Step 0: Prepare work folder and boto3 17 | #### Install boto3 library 18 | - Open your terminal 19 | - Run this command 20 | ```bash 21 | pip install boto3 22 | ``` 23 | 24 | #### Preparing work folder 25 | - Navigate to `work/` folder 26 | - You will find 2 sub-directories named `cdk` and `lambda-functions` 27 | - Navigate to `work/lambda-functions/` 28 | - You will find 3 sub-directories of AWS Lambda Functions. 29 | 30 | ### Step 1: Working on `secure-api` Lambda Function 31 | **You don't need to do anything.** This is already been provided in the `work/lambda-functions/secure-api/app.py` file as a complete code. 32 | 33 | ### Step 2: Working on `token-authorizer` Lambda Function 34 | - Navigate to `work/lambda-functions/token-authorizer/` 35 | - Open `app.py` 36 | 37 | ### Step 3: Verify JWT Token 38 | - In `verify-token` function, you need to complete the code by decoding the JWT token and verifying using the provided secret and algorithm. 39 | - Use `data` as the variable name for the decoded JWT token 40 | 41 | > **💡 HINT** 42 | > - This lab uses PyJWT library. Here's the link to see how to decode the token. [Link](https://pyjwt.readthedocs.io/en/latest/usage.html#encoding-decoding-tokens-with-hs256) 43 | 44 | > ### 😕 Are you stuck? 45 | > See the solution [here](https://github.com/donnieprakoso/workshop-restAPI/blob/main/3-lab-controlAccessAPI/source/lambda-functions/token-authorizer/app.py) 46 | 47 | ### Step 4: Define a policy document and policy statement 48 | - Your Lambda authorizer, needs to return output in a dictionary object so API Gateway knows if the request is allowed or denied. 49 | - To make it easier for you defining the object, you can follow the JSON object defined in the working file. 50 | 51 | > **💡 HINT** 52 | > - To see the complete reference and details, click on this [link](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-output.html). 53 | 54 | > ### 😕 Are you stuck? 55 | > See the solution [here](https://github.com/donnieprakoso/workshop-restAPI/blob/main/3-lab-controlAccessAPI/source/lambda-functions/token-authorizer/app.py) 56 | 57 | ### Step 5: Working on the AWS CDK App 58 | #### Navigate to work folder 59 | - Navigate to `work/cdk/` 60 | - Create a file named `cdk.json` 61 | - Open `cdk.json` and write these lines. These lines are to give a set of instruction for AWS CDK on how to build this app. 62 | ```json 63 | { 64 | "app":"python3 app.py", 65 | "context":{} 66 | } 67 | ``` 68 | 69 | - Open `app.py`. In this file, you see that we are missing few things to make this as a complete code. Read the file thoroughly and go to the next steps below. 70 | 71 | ### Step 6: Create TokenAuthorizer object 72 | - Create `TokenAuthorizer` object and use the handler set the `handler` to the `authorizer` Lambda function. 73 | 74 | > **💡 HINT** 75 | > - Use TokenAuthorizer construct to createthe object. Here's the [API reference](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_apigateway/TokenAuthorizer.html). 76 | 77 | > ### 😕 Are you stuck? 78 | > See the solution [here](https://github.com/donnieprakoso/workshop-restAPI/blob/main/3-lab-controlAccessAPI/source/cdk/app.py) 79 | 80 | ### Step 7: Adding GET method and setting the authorizer 81 | - In above lines, you'll see that we've defined the resource using `add_resource()` and set the URL resource to `api`. This means that the request will need to go to `/api` endpoint. 82 | - To control the access, you will need to add the authorizer when you `add_method` for your API. 83 | 84 | > **💡 HINT** 85 | > - Use `authorizer` parameter to set the authorizer. Here's the [API reference](https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_apigateway/IResource.html#aws_cdk.aws_apigateway.IResource.add_method). 86 | 87 | > ### 😕 Are you stuck? 88 | > See the solution [here](https://github.com/donnieprakoso/workshop-restAPI/blob/main/3-lab-controlAccessAPI/source/cdk/app.py) 89 | 90 | ### Step 8: Install `token-authorizer` function library 91 | - As the `token-authorizer` uses external library, we need to install the libraries into the folder so CDK can deploy the function alongside with the libraries. 92 | - Open your terminal 93 | - Navigate to `work/lambda-functions/token-authorizer/` 94 | - Open `requirements.txt` 95 | - Check if there's `PyJWT` on the first line, if not, you'll need to add it 96 | - Install the requirements into the folder using following command: `pip install -r requirements.txt -t .` 97 | 98 | > **💡 HINT** 99 | > - If you're new to AWS Lambda, the following link will help you to understand how to package your Lambda function if you have external runtime dependencies. [Link](https://docs.aws.amazon.com/lambda/latest/dg/python-package-create.html#python-package-create-with-dependency) 100 | 101 | 102 | ### Step 9: Install all required libraries to build and run CDK app 103 | - Open your terminal 104 | - Navigate to `work/cdk/` 105 | - Create a file named `requirements.txt`. This is a standard method to install any dependencies for Python applications. 106 | - Add these lines: 107 | ``` 108 | aws-cdk.core==1.96.0 109 | aws-cdk.aws-iam==1.96.0 110 | aws-cdk.aws-lambda==1.96.0 111 | aws-cdk.aws-apigateway==1.96.0 112 | ``` 113 | 114 | - Install the libraries by executing: 115 | ```bash 116 | pip3 install -r requirements.txt 117 | ``` 118 | 119 | ### Step 10: Deploy 120 | - Open your terminal 121 | - Navigate to `work/cdk/` 122 | - Deploy the app by executing: 123 | ```bash 124 | cdk deploy 125 | ``` 126 | - When you successfully deployed the CDK APP, you will get an API endpoint running on Amazon API Gateway. **Note down this URL.** 127 | 128 | #### ⚠️ Time for Testing ⚠️ 129 | - For testing, we're going to do 2 scenarios: 130 | 1. Using random string as the authorization token — which will make the requests failed 131 | 2. Using JWT token which will led to successful request 132 | 133 | #### ⚠️ Wait, what JWT token? ⚠️ 134 | - Use the following JWT token that I generated from [JWT.io](https://jwt.io): 135 | ``` 136 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwidXNlcl9pZCI6InVzZXItdGVzdC1qd3QiLCJpYXQiOjE1MTYyMzkwMjJ9.c9BqfJp04T9E3ulM97ruVmedwhT4AWyGfL3dPCSrSr4 137 | ``` 138 | - If you remember when you were working on `token-authorizer` function, we have defined the JWT token secret. 139 | - You also can decode the JWT token at [JWT.io](https://jwt.io) by copy and paste the token, and also setting the secret from the Lambda function 140 | 141 | ### Step 11: Testing with random string 142 | - Open terminal 143 | - Execute this command: 144 | ```bash 145 | curl -H "Authorization: THIS_IS_A_RANDOM_STRING" 146 | ``` 147 | - If you see the following response, then your authorizer works correctly 148 | ```json 149 | {"Message":"User is not authorized to access this resource with an explicit deny"} 150 | ``` 151 | 152 | ### Step 12: Testing with correct token 153 | - Open terminal 154 | - Execute this command: 155 | ```bash 156 | curl -H "Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwidXNlcl9pZCI6InVzZXItdGVzdC1qd3QiLCJpYXQiOjE1MTYyMzkwMjJ9.c9BqfJp04T9E3ulM97ruVmedwhT4AWyGfL3dPCSrSr4" 157 | ``` 158 | - If you see the following response, then your authorizer works correctly. 159 | ```json 160 | {"message": "success"} 161 | ``` 162 | - This is the response from your `secure-api` Lambda function 163 | 164 | # 🤘🏻Congrats! 165 | You've just finished the Lab 3. 166 | 167 | ## Cleaning Up 168 | To clean up all resources, follow these instructions below: 169 | 1. Go to `work/cdk/` 170 | 2. Run `cdk destroy` command 171 | ```bash 172 | cdk destroy 173 | ``` -------------------------------------------------------------------------------- /3-lab-controlAccessAPI/lab3-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donnieprakoso/workshop-restAPI/b3287d5749b65648710dde4e736ba55b73371c6b/3-lab-controlAccessAPI/lab3-diagram.png -------------------------------------------------------------------------------- /3-lab-controlAccessAPI/source/cdk/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donnieprakoso/workshop-restAPI/b3287d5749b65648710dde4e736ba55b73371c6b/3-lab-controlAccessAPI/source/cdk/README.md -------------------------------------------------------------------------------- /3-lab-controlAccessAPI/source/cdk/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from aws_cdk import aws_iam as _iam 4 | from aws_cdk import aws_lambda as _lambda 5 | from aws_cdk import aws_apigateway as _ag 6 | from aws_cdk import core 7 | 8 | 9 | class CdkStack(core.Stack): 10 | def __init__(self, scope: core.Construct, id: str, stack_prefix: str, **kwargs) -> None: 11 | super().__init__(scope, id, **kwargs) 12 | 13 | # IAM Roles 14 | lambda_role = _iam.Role( 15 | self, 16 | id='{}-lambda-role'.format(stack_prefix), 17 | assumed_by=_iam.ServicePrincipal('lambda.amazonaws.com')) 18 | 19 | cw_policy_statement = _iam.PolicyStatement(effect=_iam.Effect.ALLOW) 20 | cw_policy_statement.add_actions("logs:CreateLogGroup") 21 | cw_policy_statement.add_actions("logs:CreateLogStream") 22 | cw_policy_statement.add_actions("logs:PutLogEvents") 23 | cw_policy_statement.add_actions("logs:DescribeLogStreams") 24 | cw_policy_statement.add_resources("*") 25 | lambda_role.add_to_policy(cw_policy_statement) 26 | 27 | # AWS Lambda Functions 28 | fnLambda_secureAPI = _lambda.Function( 29 | self, 30 | "{}-function-secureApi".format(stack_prefix), 31 | code=_lambda.AssetCode("../lambda-functions/secure-api"), 32 | handler="app.handler", 33 | timeout=core.Duration.seconds(60), 34 | role=lambda_role, 35 | runtime=_lambda.Runtime.PYTHON_3_8) 36 | 37 | fnLambda_authorizer = _lambda.Function( 38 | self, 39 | "{}-function-tokenAuthorizer".format(stack_prefix), 40 | code=_lambda.AssetCode("../lambda-functions/token-authorizer"), 41 | handler="app.handler", 42 | timeout=core.Duration.seconds(60), 43 | role=lambda_role, 44 | runtime=_lambda.Runtime.PYTHON_3_8) 45 | 46 | api = _ag.RestApi( 47 | self, 48 | id="{}-api-gateway".format(stack_prefix), 49 | ) 50 | 51 | api_authorizer = _ag.TokenAuthorizer(self, id="{}-authorizer".format(stack_prefix), 52 | handler=fnLambda_authorizer) 53 | 54 | int_secure_api = _ag.LambdaIntegration(fnLambda_secureAPI) 55 | 56 | res_data = api.root.add_resource('api') 57 | res_data.add_method('GET',int_secure_api, authorizer = api_authorizer) 58 | 59 | core.CfnOutput(self, "{}-output-apiEndpointURL".format(stack_prefix), 60 | value=api.url, export_name="{}-apiEndpointURL".format(stack_prefix)) 61 | 62 | 63 | stack_prefix = 'restAPI-lab3-secureAPI' 64 | app = core.App() 65 | stack = CdkStack(app, stack_prefix, stack_prefix=stack_prefix) 66 | core.Tags.of(stack).add('Name', stack_prefix) 67 | 68 | app.synth() 69 | -------------------------------------------------------------------------------- /3-lab-controlAccessAPI/source/cdk/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "python3 app.py" 3 | } 4 | -------------------------------------------------------------------------------- /3-lab-controlAccessAPI/source/cdk/requirements.txt: -------------------------------------------------------------------------------- 1 | aws-cdk.core==1.96.0 2 | aws-cdk.aws-iam==1.96.0 3 | aws-cdk.aws-lambda==1.96.0 4 | aws-cdk.aws-apigateway==1.96.0 5 | -------------------------------------------------------------------------------- /3-lab-controlAccessAPI/source/lambda-functions/secure-api/app.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | def handler(event, context): 4 | response = {"statusCode": 200, "body": json.dumps({"message": "success"})} 5 | return response 6 | -------------------------------------------------------------------------------- /3-lab-controlAccessAPI/source/lambda-functions/token-authorizer/app.py: -------------------------------------------------------------------------------- 1 | import json 2 | import jwt 3 | 4 | def verify_token(token): 5 | try: 6 | JWT_SECRET = 'pssstttitsasecret' 7 | JWT_ALGORITHM = 'HS256' 8 | data = jwt.decode(token, JWT_SECRET, JWT_ALGORITHM) 9 | return data 10 | except: 11 | return None 12 | 13 | def generate_policy(principal_ID, effect, resource): 14 | auth_response = {}; 15 | 16 | auth_response['principalId'] = principal_ID; 17 | if effect and resource: 18 | policy_document = {} 19 | policy_document['Version'] = '2012-10-17' 20 | policy_document['Statement'] = [] 21 | policy_statement = {} 22 | policy_statement['Action'] = 'execute-api:Invoke' 23 | policy_statement['Effect'] = effect 24 | policy_statement['Resource'] = resource 25 | policy_document['Statement']= [policy_statement] 26 | auth_response['policyDocument'] = policy_document 27 | 28 | auth_response['context'] = {} 29 | return auth_response 30 | 31 | def handler(event, context): 32 | token = event['authorizationToken'] if 'authorizationToken' in event else None 33 | if token: 34 | data = verify_token(token) 35 | if data: 36 | auth_response = generate_policy(data['user_id'], 'Allow', event['methodArn']) 37 | return auth_response 38 | else: 39 | auth_response = generate_policy('', 'Deny', event['methodArn']) 40 | return auth_response 41 | 42 | -------------------------------------------------------------------------------- /3-lab-controlAccessAPI/source/lambda-functions/token-authorizer/requirements.txt: -------------------------------------------------------------------------------- 1 | PyJWT -------------------------------------------------------------------------------- /3-lab-controlAccessAPI/work/README.md: -------------------------------------------------------------------------------- 1 | # Work Folder 2 | Please work on the workshop activities in this folder. The purpose is for you to have a separate space to develop while still being able to see the primary reference. 3 | -------------------------------------------------------------------------------- /3-lab-controlAccessAPI/work/cdk/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donnieprakoso/workshop-restAPI/b3287d5749b65648710dde4e736ba55b73371c6b/3-lab-controlAccessAPI/work/cdk/.gitignore -------------------------------------------------------------------------------- /3-lab-controlAccessAPI/work/cdk/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from aws_cdk import aws_iam as _iam 4 | from aws_cdk import aws_lambda as _lambda 5 | from aws_cdk import aws_apigateway as _ag 6 | from aws_cdk import core 7 | 8 | 9 | class CdkStack(core.Stack): 10 | def __init__(self, scope: core.Construct, id: str, stack_prefix: str, **kwargs) -> None: 11 | super().__init__(scope, id, **kwargs) 12 | 13 | # IAM Roles 14 | lambda_role = _iam.Role( 15 | self, 16 | id='{}-lambda-role'.format(stack_prefix), 17 | assumed_by=_iam.ServicePrincipal('lambda.amazonaws.com')) 18 | 19 | cw_policy_statement = _iam.PolicyStatement(effect=_iam.Effect.ALLOW) 20 | cw_policy_statement.add_actions("logs:CreateLogGroup") 21 | cw_policy_statement.add_actions("logs:CreateLogStream") 22 | cw_policy_statement.add_actions("logs:PutLogEvents") 23 | cw_policy_statement.add_actions("logs:DescribeLogStreams") 24 | cw_policy_statement.add_resources("*") 25 | lambda_role.add_to_policy(cw_policy_statement) 26 | 27 | # AWS Lambda Functions 28 | fnLambda_secureAPI = _lambda.Function( 29 | self, 30 | "{}-function-secureApi".format(stack_prefix), 31 | code=_lambda.AssetCode("../lambda-functions/secure-api"), 32 | handler="app.handler", 33 | timeout=core.Duration.seconds(60), 34 | role=lambda_role, 35 | runtime=_lambda.Runtime.PYTHON_3_8) 36 | 37 | fnLambda_authorizer = _lambda.Function( 38 | self, 39 | "{}-function-tokenAuthorizer".format(stack_prefix), 40 | code=_lambda.AssetCode("../lambda-functions/token-authorizer"), 41 | handler="app.handler", 42 | timeout=core.Duration.seconds(60), 43 | role=lambda_role, 44 | runtime=_lambda.Runtime.PYTHON_3_8) 45 | 46 | api = _ag.RestApi( 47 | self, 48 | id="{}-api-gateway".format(stack_prefix), 49 | ) 50 | 51 | ''' 52 | [TASK] Create TokenAuthorizer object 53 | ''' 54 | ''' 55 | [END of TASK] 56 | ''' 57 | 58 | int_secure_api = _ag.LambdaIntegration(fnLambda_secureAPI) 59 | 60 | res_data = api.root.add_resource('api') 61 | ''' 62 | [TASK] Add method GET for the `secure-api` and set the authorizer 63 | ''' 64 | ''' 65 | [END of TASK] 66 | ''' 67 | 68 | core.CfnOutput(self, "{}-output-apiEndpointURL".format(stack_prefix), 69 | value=api.url, export_name="{}-apiEndpointURL".format(stack_prefix)) 70 | 71 | 72 | stack_prefix = 'restAPI-lab3-secureAPI' 73 | app = core.App() 74 | stack = CdkStack(app, stack_prefix, stack_prefix=stack_prefix) 75 | core.Tags.of(stack).add('Name', stack_prefix) 76 | 77 | app.synth() 78 | -------------------------------------------------------------------------------- /3-lab-controlAccessAPI/work/lambda-functions/secure-api/app.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | def handler(event, context): 4 | response = {"statusCode": 200, "body": json.dumps({"message": "success"})} 5 | return response 6 | -------------------------------------------------------------------------------- /3-lab-controlAccessAPI/work/lambda-functions/token-authorizer/app.py: -------------------------------------------------------------------------------- 1 | import json 2 | import jwt 3 | 4 | def verify_token(token): 5 | try: 6 | JWT_SECRET = 'pssstttitsasecret' 7 | JWT_ALGORITHM = 'HS256' 8 | ''' 9 | [TASK] Verify JWT token 10 | ''' 11 | ''' 12 | [END of TASK] 13 | ''' 14 | return data 15 | except: 16 | return None 17 | 18 | 19 | 20 | def generate_policy(principal_ID, effect, resource): 21 | auth_response = {}; 22 | 23 | auth_response['principalId'] = principal_ID; 24 | if effect and resource: 25 | ''' 26 | [TASK] Define a policy document and add a policy statement 27 | ''' 28 | ''' 29 | { 30 | "principalId": "", 31 | "policyDocument": { 32 | "Version": "2012-10-17", 33 | "Statement": [ 34 | { 35 | "Action": "execute-api:Invoke", 36 | "Effect": "", 37 | "Resource": "" 38 | } 39 | ] 40 | }, 41 | "context": {} 42 | } 43 | ''' 44 | ''' 45 | [END of TASK] 46 | ''' 47 | auth_response['policyDocument'] = policy_document 48 | 49 | auth_response['context'] = {} 50 | return auth_response 51 | 52 | 53 | def handler(event, context): 54 | token = event['authorizationToken'] if 'authorizationToken' in event else None 55 | if token: 56 | data = verify_token(token) 57 | if data: 58 | auth_response = generate_policy(data['user_id'], 'Allow', event['methodArn']) 59 | return auth_response 60 | else: 61 | auth_response = generate_policy('', 'Deny', event['methodArn']) 62 | return auth_response 63 | 64 | -------------------------------------------------------------------------------- /3-lab-controlAccessAPI/work/lambda-functions/token-authorizer/requirements.txt: -------------------------------------------------------------------------------- 1 | PyJWT -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright http://Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "[]" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright [yyyy] [name of copyright owner] 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 🚀 Workshop — Building REST APIs with AWS 2 | 3 | In this workshop, you will build and deploy REST APIs using Amazon API Gateway, AWS Lambda, and AWS CDK (and a bit of Amazon DynamoDB). 4 | The main objective of this workshop is to build a foundation of understanding how to implement REST API with AWS and also how to adopt IaC for reusable APIs. 5 | 6 | The content of this workshop will be updated regularly and if you have questions or find issues in this workshop, please file them as an Issue. 7 | 8 | ## Workshop Structure 9 | Each lab in this workshop can be run separately. Each labs consist of full working source code along with AWS CDK to easily provision and destroy created resources. 10 | 11 | #### Lab 1: Integrating AWS Lambda with Amazon DynamoDB 12 | The objective of this lab is to help you understand how to develop AWS Lambda to perform some activities with Amazon DynamoDB. This is considered as a good exercise — as you develop APIs with AWS serverless services — you will encounter many use-cases that requires integration between AWS Lambda and Amazon DynamoDB. 13 | 14 | In this workshop, you will build 2 AWS Lambda Functions. One AWS Lambda Function will store data, and another Lambda Function to retrieve all data from a single table in Amazon DynamoDB. This is a basic exercise to help you understand how to perform basic data storing and retrieval from Lambda functions. All of resources needed in this lab will be provisioned by AWS Cloud Development Kit (CDK). 15 | 16 | [💻 Start This Lab](https://github.com/donnieprakoso/workshop-restAPI/tree/main/1-lab-lambdaDynamoDB) 17 | 18 | #### Lab 2: Implementing REST API with Amazon API Gateway 19 | This lab is the main content of this workshop where you are going to implement REST API with Amazon API Gateway, AWS Lambda and Amazon DynamoDB. You will learn how to build resources following REST API guidelines using HTTP protocol and methods. 20 | 21 | You will build 4 AWS Lambda Functions, each of them are integrated with Amazon API Gateway endpoint and respective HTTP method. There are few small details in this lab that you'll find useful while building your API, for examples: defining resources and how to get URL parameters. 22 | 23 | [💻 Start This Lab](https://github.com/donnieprakoso/workshop-restAPI/tree/main/2-lab-apiGatewayIntegration) 24 | 25 | #### Lab 3: Controlling Access with Lambda Authorizer 26 | 27 | In this lab, you will build a basic API that needs to be secured by Lambda Authorizer using token. The token takes form of JWT. Please be noted that in this lab we will not cover authentication and authorizatio platform. 28 | 29 | You will learn how to use Lambda Authorizer and how to integrate with Amazon API Gateway using AWS CDK, to control the access of your API. 30 | 31 | [💻 Start This Lab](https://github.com/donnieprakoso/workshop-restAPI/tree/main/3-lab-controlAccessAPI) 32 | 33 | --- 34 | ## Workshop Level 35 | This workshop welcomes developers of all levels. 36 | 37 | This workshop is structured as puzzles in which you need to complete a set of partial codes into a complete code. It is an intended design to help build an understanding of a specific concept. Also, to help you get familiar with common resources needed to develop with AWS services. 38 | 39 | --- 40 | ## 🛑 First Thing First 41 | If this is your first time using this workshop, this section is an important piece that you need to read before moving on. 42 | 43 | ⚠️ 44 | > Please make sure that your development environment meets the requirements below and properly configured before starting any of the workshops. 45 | 46 | **Workshop Requirements** 47 | 48 | Requirement | More Information | Notes 49 | ---|---|--- 50 | Active AWS Account | [Link](https://aws.amazon.com/) | Mandatory requirement 51 | AWS CDK | [Link](https://aws.amazon.com/cdk/) |Require Node JS 52 | AWS CLI | [Link](https://aws.amazon.com/cli/) |Require active AWS account. Please configure your account as described on this [page](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) 53 | Python 3.8 | [Link](https://www.python.org/downloads/release/python-380/) |Most of the workshop will be using Python 3.8 54 | Boto3 | [Link](https://aws.amazon.com/sdk-for-python/) | Amazon Web Services (AWS) Software Development Kit (SDK) for Python 55 | Node JS 10.30 or later | [Link](https://nodejs.org/en/download/current/) |Node.js versions 13.0.0 through 13.6.0 are not compatible with the AWS CDK 56 | 57 | 58 | ⚠️ 59 | > Since we will be using AWS CDK extensively in this workshop, please properly configure AWS CDK for your development environment. 60 | 61 | **If you haven't done that, please follow the instruction [here](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html).** 62 | 63 | In summary, here's a quick checklist to complete the required checking. 64 | - [ ] Installed AWS CLI 65 | - [ ] Configured AWS CLI with `aws configure` 66 | - [ ] Installed Node JS 67 | - [ ] Installed AWS CDK with `npm install -g aws-cdk` 68 | - [ ] Configured AWS CDK with `cdk bootstrap` 69 | 70 | ### **💡 HINT** and 😕 Are you stuck? 71 | For the more complex tasks that you need to complete, there will be a **💡 HINT** to guide you on how to solve it. Most of the time, it will also include link(s) for further reading. 72 | 73 | Please remember that if you are stuck and can't move to the next step, you can always see the main reference file to see the solution. For easy access, **😕 Are you stuck?** will guide you directly to the solution. 74 | 75 | ## AWS Services 76 | Some of the services from AWS that are used in this workshop are as follows: 77 | - [AWS CDK](https://aws.amazon.com/cdk/) 78 | - [AWS Lambda](https://aws.amazon.com/lambda/) 79 | - [Amazon API Gateway](https://aws.amazon.com/api-gateway/) 80 | - [Amazon CloudWatch](https://aws.amazon.com/cloudwatch/) 81 | - [Amazon DynamoDB](https://aws.amazon.com/dynamodb/) 82 | 83 | ## ⚠️ Cleaning Up 84 | This workshop uses AWS services that are mostly covered by the Free Tier allowance - ONLY if your account is less than 12 months old. For accounts passed the free tier eligibility, it may incur some costs. To minimize the cost, make sure you **delete resources used in this workshop when you are finished**. 85 | 86 | All of the labs in this workshop use a standardized cleaning method with AWS CDK. 87 | 1. Go to each lab 88 | 2. Change the directory to `cdk /` 89 | 3. Run `cdk destroy` 90 | 4. If in some cases it fails, you need to go to [AWS CloudFormation](https://console.aws.amazon.com/cloudformation/) to manually delete the stack. 91 | 92 | --------------------------------------------------------------------------------