# Build a GraphQL API on AWS with CDK, Python, AppSync, and DynamoDB(Part 2)

Hi, Welcome to part 2 of this post series.
In [Part 1](https://phatrabbitapps.com/build-a-graphql-api-on-aws-with-cdk-python-appsync-and-dynamodbpart-1), we installed and created a CDK application alongside all was constructs needed to build this application. </br>
We also went ahead to write some code to initialize and use a couple of constructs. 

</br>
In this post, we will continue from where we left off in Part 1. Adding Resolvers. 
Resolvers are functions that connect fields of the graphQL schema to a data source. In our case, the data source is the DynamoDB we created.

</br>
Let's begin
<h3> Create Trainer Resolver </h3>
Create a folder in the root directory called **resolver_functions**.  </br>
Within that folder, create a text file called **create_trainer** and type in the following code

```
 {
                "version": "2017-02-28",
                "operation": "PutItem",
                "key": {
                    "id": { "S": "$util.autoId()" }
                },
                "attributeValues": {
                    "firstName": $util.dynamodb.toDynamoDBJson($ctx.args.firstName),
                    "lastName": $util.dynamodb.toDynamoDBJson($ctx.args.lastName),
                    "age": $util.dynamodb.toDynamoDBJson($ctx.args.age),
                    "specialty": $util.dynamodb.toDynamoDBJson($ctx.args.specialty)


                }
            }
``` 
This piece of code is a request mapping template that creates a trainer with an auto-generated ID,firstName,lastName, age, and specialty and saves to our DynamoDB table. </br>
Take note of the **operation:PutItem**.

</br>
Now, let's attach this template to a resolver function.
</br>

In your **cdk_trainer_stack.py**, type in 

```

with open(os.path.join(dirname, "../resolver_functions/create_trainer"), 'r') as file:
            create_trainer = file.read().replace('\n', '')  

create_trainers_resolver = CfnResolver(
            self, 'CreateTrainerMutationResolver',
            api_id=trainers_graphql_api.attr_api_id,
            type_name='Mutation',
            field_name='createTrainer',
            data_source_name=data_source.name,
            request_mapping_template=create_trainer,
            response_mapping_template="$util.toJson($ctx.result)"
        )

        create_trainers_resolver.add_depends_on(api_schema)

``` 
If you recall, in part 1, we had a **createTrainer** method in our mutation type. 

</br>
So the **type_name** is Mutation, **field_name** is createTrainer, data_source_name is the name of the DB table, we pass in our template as a string to **request_mapping_template** and return a response(**$util.toJson($ctx.result)**) through a **response_mapping_template**
</br>
<h2> Update Trainer Resolver </h2>
Create a file in the **resolver_functions** directory called update_trainer and type in the following code.

```
 {
                "version": "2017-02-28",
                "operation": "UpdateItem",
                "key":{
                    "id":$util.dynamodb.toDynamoDBJson($ctx.args.id)
                },
                "update":{

                "expression": "SET firstName = :firstName,lastName = :lastName, #ageField =:age,specialty = :specialty",

                "expressionNames": {
                "#ageField": "age"
                },
                "expressionValues": {
                ":firstName": $util.dynamodb.toDynamoDBJson($ctx.args.firstName),
                ":lastName": $util.dynamodb.toDynamoDBJson($ctx.args.lastName),
                ":age": $util.dynamodb.toDynamoDBJson($ctx.args.age),
                ":specialty": $util.dynamodb.toDynamoDBJson($ctx.args.specialty)
                }
                }


            }
``` 
The above template simply updates trainer information, based on their ID.
</br>
Here's how to attach that template to a resolver function

```
with open(os.path.join(dirname, "../resolver_functions/update_trainer"), 'r') as file:
            update_trainer = file.read().replace('\n', '')

update_trainers_resolver = CfnResolver(
            self,'UpdateMutationResolver',
            api_id=trainers_graphql_api.attr_api_id,
            type_name="Mutation",
            field_name="updateTrainers",
            data_source_name=data_source.name,
            request_mapping_template=update_trainer,
            response_mapping_template="$util.toJson($ctx.result)"
        )
        update_trainers_resolver.add_depends_on(api_schema)

``` 

<h2> Delete Trainer </h2>
Create a delete_trainer text file in **resolver_functions** folder and type in this piece of code.

```
{
                "version": "2017-02-28",
                "operation": "DeleteItem",
                "key": {
                "id": $util.dynamodb.toDynamoDBJson($ctx.args.id)
                }
            }
``` 
This template deletes a trainer record based on their ID.
</br>
Here's it's corresponding resolver function.

```
        with open(os.path.join(dirname, "../resolver_functions/delete_trainer"), 'r') as file:
            delete_trainer = file.read().replace('\n', '') 

        delete_trainer_resolver = CfnResolver(
            self, 'DeleteMutationResolver',
            api_id=trainers_graphql_api.attr_api_id,
            type_name='Mutation',
            field_name='deleteTrainer',
            data_source_name=data_source.name,
            request_mapping_template=delete_trainer,
            response_mapping_template="$util.toJson($ctx.result)"
        )

        delete_trainer_resolver.add_depends_on(api_schema)
``` 
<h2> Get All Trainers </h2>
Create a text file called **all_trainers** in the **resolver_functions** folder and type in the following code.

```
{
                "version": "2017-02-28",
                "operation": "Scan",
                "limit": $util.defaultIfNull($ctx.args.limit, 20),
                "nextToken": $util.toJson($util.defaultIfNullOrEmpty($ctx.args.nextToken, null))
            }
``` 
This template gets all trainers as a paginated list in groups of 20.

```
 with open(os.path.join(dirname, "../resolver_functions/all_trainers"), 'r') as file:
            all_trainers = file.read().replace('\n', '')

 get_all_trainers_resolver = CfnResolver(
            self, 'GetAllQueryResolver',
            api_id=trainers_graphql_api.attr_api_id,
            type_name='Query',
            field_name='allTrainers',
            data_source_name=data_source.name,
            request_mapping_template=all_trainers,
            response_mapping_template="$util.toJson($ctx.result)"
        )

        get_all_trainers_resolver.add_depends_on(api_schema)
     
``` 

<h2> Get A Single Trainer </h2>
Create a text file called **get_trainer** in the **resolver_functions** folder and type in the following code.

```
{
                "version": "2017-02-28",
                "operation": "GetItem",
                "key": {
                "id": $util.dynamodb.toDynamoDBJson($ctx.args.id)
                }
            }
``` 
We are getting a single trainer based on their ID.


```
  with open(os.path.join(dirname, "../resolver_functions/get_trainer"), 'r') as file:
            get_trainer = file.read().replace('\n', '')  

  get_Trainer_resolver = CfnResolver(
            self, 'GetOneQueryResolver',
            api_id=trainers_graphql_api.attr_api_id,
            type_name='Query',
            field_name='getTrainer',
            data_source_name=data_source.name,
            request_mapping_template=get_trainer,
            response_mapping_template="$util.toJson($ctx.result)"
        )

        get_Trainer_resolver.add_depends_on(api_schema)

``` 
And that's it. 

</br>
You can grab the complete code from [GitHub](https://github.com/trey-rosius/cdkTrainer)

**<h2> Synthesize a template </h2>**
AWS CDK apps are effectively only a definition of your infrastructure using code. When CDK apps are executed, they produce (or “synthesize”, in CDK parlance) an AWS CloudFormation template for each stack defined in your application.

To synthesize a CDK app, use the **cdk synth** command.

</br>
Let’s check out the template synthesized from this app. 

</br>
From the project root directory, activate your virtual environment using 

For MacOS/Linux 
```
source .venv/bin/activate
``` 

If you are a Windows platform, you would activate the virtualenv like this:

```
% .venv\Scripts\activate.bat
```
Synthesize your cdk stack using 

```
cdk synth
``` 
Here's my output of the cloud formation template(CdkTrainerStack.template.json), located in the **cdk.out** folder. 


```
{
  "Resources": {
    "trainersApi": {
      "Type": "AWS::AppSync::GraphQLApi",
      "Properties": {
        "AuthenticationType": "API_KEY",
        "Name": "trainers-api"
      },
      "Metadata": {
        "aws:cdk:path": "CdkTrainerStack/trainersApi"
      }
    },
    "TrainersApiKey": {
      "Type": "AWS::AppSync::ApiKey",
      "Properties": {
        "ApiId": {
          "Fn::GetAtt": [
            "trainersApi",
            "ApiId"
          ]
        }
      },
      "Metadata": {
        "aws:cdk:path": "CdkTrainerStack/TrainersApiKey"
      }
    },
    "TrainersSchema": {
      "Type": "AWS::AppSync::GraphQLSchema",
      "Properties": {
        "ApiId": {
          "Fn::GetAtt": [
            "trainersApi",
            "ApiId"
          ]
        },
        "Definition": "type Trainers {    id: ID!    firstName:String!    lastName:String!    age:Int!    specialty:Specialty        }enum Specialty{    BODYBUILDING,    YOUTHFITNESS,    SENIORFITNESS,    CORRECTIVEEXERCISE} type PaginatedTrainers {                    items: [Trainers!]!                    nextToken: String                }                type Query {                    allTrainers(limit: Int, nextToken: String): PaginatedTrainers!                    getTrainer(id: ID!): Trainers                }                type Mutation {                    createTrainer( firstName:String!,    lastName:String!,    age:Int!,    specialty:Specialty): Trainers                    deleteTrainer(id: ID!): Trainers                    updateTrainers(id: ID!,    firstName:String,    lastName:String,    age:Int,    specialty:Specialty):Trainers                }                type Schema {                    query: Query                    mutation: Mutation                }"
      },
      "Metadata": {
        "aws:cdk:path": "CdkTrainerStack/TrainersSchema"
      }
    },
    "TrainersTableE85CC9B1": {
      "Type": "AWS::DynamoDB::Table",
      "Properties": {
        "KeySchema": [
          {
            "AttributeName": "id",
            "KeyType": "HASH"
          }
        ],
        "AttributeDefinitions": [
          {
            "AttributeName": "id",
            "AttributeType": "S"
          }
        ],
        "BillingMode": "PAY_PER_REQUEST",
        "StreamSpecification": {
          "StreamViewType": "NEW_IMAGE"
        },
        "TableName": "trainers"
      },
      "UpdateReplacePolicy": "Delete",
      "DeletionPolicy": "Delete",
      "Metadata": {
        "aws:cdk:path": "CdkTrainerStack/TrainersTable/Resource"
      }
    },
    "TrainersDynamoDBRoleE04DDD5F": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Statement": [
            {
              "Action": "sts:AssumeRole",
              "Effect": "Allow",
              "Principal": {
                "Service": "appsync.amazonaws.com"
              }
            }
          ],
          "Version": "2012-10-17"
        },
        "ManagedPolicyArns": [
          {
            "Fn::Join": [
              "",
              [
                "arn:",
                {
                  "Ref": "AWS::Partition"
                },
                ":iam::aws:policy/AmazonDynamoDBFullAccess"
              ]
            ]
          }
        ]
      },
      "Metadata": {
        "aws:cdk:path": "CdkTrainerStack/TrainersDynamoDBRole/Resource"
      }
    },
    "TrainersDataSource": {
      "Type": "AWS::AppSync::DataSource",
      "Properties": {
        "ApiId": {
          "Fn::GetAtt": [
            "trainersApi",
            "ApiId"
          ]
        },
        "Name": "TrainersDynamoDataSource",
        "Type": "AMAZON_DYNAMODB",
        "DynamoDBConfig": {
          "AwsRegion": "us-east-2",
          "TableName": {
            "Ref": "TrainersTableE85CC9B1"
          }
        },
        "ServiceRoleArn": {
          "Fn::GetAtt": [
            "TrainersDynamoDBRoleE04DDD5F",
            "Arn"
          ]
        }
      },
      "Metadata": {
        "aws:cdk:path": "CdkTrainerStack/TrainersDataSource"
      }
    },
    "GetOneQueryResolver": {
      "Type": "AWS::AppSync::Resolver",
      "Properties": {
        "ApiId": {
          "Fn::GetAtt": [
            "trainersApi",
            "ApiId"
          ]
        },
        "FieldName": "getTrainer",
        "TypeName": "Query",
        "DataSourceName": "TrainersDynamoDataSource",
        "RequestMappingTemplate": "{                \"version\": \"2017-02-28\",                \"operation\": \"GetItem\",                \"key\": {                \"id\": $util.dynamodb.toDynamoDBJson($ctx.args.id)                }            }",
        "ResponseMappingTemplate": "$util.toJson($ctx.result)"
      },
      "DependsOn": [
        "TrainersSchema"
      ],
      "Metadata": {
        "aws:cdk:path": "CdkTrainerStack/GetOneQueryResolver"
      }
    },
    "GetAllQueryResolver": {
      "Type": "AWS::AppSync::Resolver",
      "Properties": {
        "ApiId": {
          "Fn::GetAtt": [
            "trainersApi",
            "ApiId"
          ]
        },
        "FieldName": "allTrainers",
        "TypeName": "Query",
        "DataSourceName": "TrainersDynamoDataSource",
        "RequestMappingTemplate": "{                \"version\": \"2017-02-28\",                \"operation\": \"Scan\",                \"limit\": $util.defaultIfNull($ctx.args.limit, 20),                \"nextToken\": $util.toJson($util.defaultIfNullOrEmpty($ctx.args.nextToken, null))            }",
        "ResponseMappingTemplate": "$util.toJson($ctx.result)"
      },
      "DependsOn": [
        "TrainersSchema"
      ],
      "Metadata": {
        "aws:cdk:path": "CdkTrainerStack/GetAllQueryResolver"
      }
    },
    "CreateTrainerMutationResolver": {
      "Type": "AWS::AppSync::Resolver",
      "Properties": {
        "ApiId": {
          "Fn::GetAtt": [
            "trainersApi",
            "ApiId"
          ]
        },
        "FieldName": "createTrainer",
        "TypeName": "Mutation",
        "DataSourceName": "TrainersDynamoDataSource",
        "RequestMappingTemplate": " {                \"version\": \"2017-02-28\",                \"operation\": \"PutItem\",                \"key\": {                    \"id\": { \"S\": \"$util.autoId()\" }                },                \"attributeValues\": {                    \"firstName\": $util.dynamodb.toDynamoDBJson($ctx.args.firstName),                    \"lastName\": $util.dynamodb.toDynamoDBJson($ctx.args.lastName),                    \"age\": $util.dynamodb.toDynamoDBJson($ctx.args.age),                    \"specialty\": $util.dynamodb.toDynamoDBJson($ctx.args.specialty)                }            }",
        "ResponseMappingTemplate": "$util.toJson($ctx.result)"
      },
      "DependsOn": [
        "TrainersSchema"
      ],
      "Metadata": {
        "aws:cdk:path": "CdkTrainerStack/CreateTrainerMutationResolver"
      }
    },
    "UpdateMutationResolver": {
      "Type": "AWS::AppSync::Resolver",
      "Properties": {
        "ApiId": {
          "Fn::GetAtt": [
            "trainersApi",
            "ApiId"
          ]
        },
        "FieldName": "updateTrainers",
        "TypeName": "Mutation",
        "DataSourceName": "TrainersDynamoDataSource",
        "RequestMappingTemplate": " {                \"version\": \"2017-02-28\",                \"operation\": \"UpdateItem\",                \"key\":{                    \"id\":$util.dynamodb.toDynamoDBJson($ctx.args.id)                },                \"update\":{                \"expression\": \"SET firstName = :firstName,lastName = :lastName, #ageField =:age,specialty = :specialty\",                \"expressionNames\": {                \"#ageField\": \"age\"                },                \"expressionValues\": {                \":firstName\": $util.dynamodb.toDynamoDBJson($ctx.args.firstName),                \":lastName\": $util.dynamodb.toDynamoDBJson($ctx.args.lastName),                \":age\": $util.dynamodb.toDynamoDBJson($ctx.args.age),                \":specialty\": $util.dynamodb.toDynamoDBJson($ctx.args.specialty)                }                }            }",
        "ResponseMappingTemplate": "$util.toJson($ctx.result)"
      },
      "DependsOn": [
        "TrainersSchema"
      ],
      "Metadata": {
        "aws:cdk:path": "CdkTrainerStack/UpdateMutationResolver"
      }
    },
    "DeleteMutationResolver": {
      "Type": "AWS::AppSync::Resolver",
      "Properties": {
        "ApiId": {
          "Fn::GetAtt": [
            "trainersApi",
            "ApiId"
          ]
        },
        "FieldName": "deleteTrainer",
        "TypeName": "Mutation",
        "DataSourceName": "TrainersDynamoDataSource",
        "RequestMappingTemplate": "{                \"version\": \"2017-02-28\",                \"operation\": \"DeleteItem\",                \"key\": {                \"id\": $util.dynamodb.toDynamoDBJson($ctx.args.id)                }            }",
        "ResponseMappingTemplate": "$util.toJson($ctx.result)"
      },
      "DependsOn": [
        "TrainersSchema"
      ],
      "Metadata": {
        "aws:cdk:path": "CdkTrainerStack/DeleteMutationResolver"
      }
    },
    "CDKMetadata": {
      "Type": "AWS::CDK::Metadata",
      "Properties": {
        "Analytics": "v2:deflate64:H4sIAAAAAAAAE0WMyw6CMBBFv4V9HUA2LjWYuNCFgj8wlBoq9JG2aJqm/y4PjaszOffOzSHPCsiSPb7thrZ9GqgyDELtkPakYlaNhjJSKmmdGakj5UP+bCTzU0CtrZcUwhSdDOrudjloPhcnnJknf1/TjgmcxREd1t/tdXF4MRNJ6yUK1TYQ7tgMS7gckXAUECq1upkxRnL1rlMyLWAH2+RpOd+YUTouGFQrPwDtGALgAAAA"
      },
      "Metadata": {
        "aws:cdk:path": "CdkTrainerStack/CDKMetadata/Default"
      }
    }
  }
}
``` 
As you can see, this template includes a bunch of resources
-  The trainers API_KEY
- Schema
- Data Source
- All Resolvers

**<h2> CDK DEPLOY </h2> **
Now that we've gotten a cloud formation template, it's time to deploy the application. 

</br>
The first time you deploy an AWS CDK app into an environment (account/region), you’ll need to install a “bootstrap stack”. This stack includes resources that are needed for the toolkit’s operation. 

</br>
For example, the stack includes an S3 bucket that is used to store templates and assets during the deployment process.

You can use the **cdk bootstrap** command to install the bootstrap stack into an environment

```
cdk bootstrap
``` 
After successfully installing bootstrap, deploy your app using 


```
cdk deploy
``` 
If you deploy is successful, you should get an output similar to this

![Screen Shot 2021-05-16 at 08.27.29.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1621150106122/uhWB_Bmzo.png)

**<h2>he CloudFormation Console</h2>**
CDK apps are deployed through AWS CloudFormation. Each CDK stack maps 1:1 with CloudFormation stack.

This means that you can use the AWS CloudFormation console in order to manage your stacks.

Let’s take a look at the AWS CloudFormation console.

You will likely see something like this (if you don’t, make sure you are in the correct region):


![Screen Shot 2021-05-16 at 08.30.55.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1621150475824/HTDrk5w1D.png)

Take note of **CdkTrainerStack** and **CDKToolkit** 

</br>
If you select cdkTrainerStack and open the Resources tab, you will see the physical identities of our resources:

![Screen Shot 2021-05-16 at 08.37.04.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1621150729248/-8FkRvJ71.png)


![Screen Shot 2021-05-16 at 08.37.13.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1621150787944/L35Ky3aIc.png)

**<h2> Testing all endpoints </h2>**
Navigate to AppSync in the AWS console and select the API we just created

![Screen Shot 2021-05-16 at 08.41.37.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1621150996326/jEgw_sNzs.png)

Select query on the left side of the screen and add a couple of users to your DB using the  **CreateTrainer** mutation.


![Screen Shot 2021-05-16 at 08.43.57.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1621151394900/Q8tPeISBB.png)

Navigate to DynamoDb, click on the trainers table and see all items created.

![Screen Shot 2021-05-16 at 08.50.30.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1621151478727/1iGVYn6hs.png)

You can also get all trainers from your DB like so

![Screen Shot 2021-05-16 at 08.44.59.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1621151540810/-fu4YtdAJ.png)

As an exercise, go ahead and test **updateTrainer** , **deleteTrainer**, **GetTrainer**.


**<h2> Clean Up Your Stack </h2>**
To avoid unexpected charges to your account, make sure you clean up your CDK stack.

You can either delete the stack through the AWS CloudFormation console or use

 
```
cdk destroy
``` 

**</h2> Conclusion </h2>**
In this post series, we built a GraphQl API using CDK and python, with AWS constructs such as 
- AppSync
- DynamoDB
- IAM

</br>
Let me know what you think about this piece. I'll also love to know what I should improve on. </br>
Thanks for check this out. </br>
In the next article, we will automate this API by creating a CI/CD pipeline to automatically build and deploy the app when we commit to Github. </br>
So stay tuned.

</br>
Till next time my brothers and sisters ✌🏿



 
