Exploring AWS CDK, AWS AppSync, and Amazon DynamoDB
Tags: aws, cdk, beginners
Published: 2022-11-20 11:02:23

## Introduction

Hello everyone! I want to share my experience of exploring AWS CDK. I want to deploy AWS AppSync and Amazon DynamoDB. Let's learn with me.

## Background

I used Terraform for provisioning the resources into Heroku. Heroku announced that they will stop the free plan, so I want to move my resources into AWS. I learned AWS SDK 3 months ago, but got stuck. I hope this post will help you.

* [Previous Version](https://github.com/bervProject/my-personal-web-api-infra/tree/cc60e10fbea615b27c7556205ed02fec306b2555)

## Preparation

1. Install Node.js
2. Install AWS CDK Toolkit. Command: `npm install -g aws-cdk`. [More informations](https://docs.aws.amazon.com/cdk/v2/guide/cli.html).
3. If you want to generate a new project. You can use `cdk init app --language csharp`. [More informations](https://docs.aws.amazon.com/cdk/v2/guide/hello_world.html).

## Update the Stack

I want simple architecture. You can see my architecture below.

![arch](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sgkhbfd9ep0y6cbik7iw.png)

* I have GraphQL schema like this:

```graphql
type Experience {
  id: ID!
  title: String!
}

input ExperienceInput {
  title: String!
}

input UpdateExperienceInput {
  id: ID!
  title: String
}

type Query {
  getExperienceById(experienceId: String!): Experience
  getExperiences: [Experience]
}

type Mutation {
  addExperience(input: ExperienceInput!): Experience
  updateExperience(input: UpdateExperienceInput!): Experience
  deleteExperience(experienceId: String!): String
}
```

Our schema only has simple CRUD and store experience data. The experience data has id and title only. You can extend more columns on it, but take note you will need to update the CDK code too.

* My CDK stacks look like this:

```csharp
using Amazon.CDK;
using Amazon.CDK.AWS.AppSync.Alpha;
using Amazon.CDK.AWS.DynamoDB;
using Amazon.CDK.AWS.IAM;
using Constructs;

namespace MyPersonalWebApiInfra
{
    public class MyPersonalWebApiInfraStack : Stack
    {
        internal MyPersonalWebApiInfraStack(Construct scope, string id, IStackProps props = null) : base(scope, id,
            props)
        {
            // AppSync
            var api = new GraphqlApi(this, "api", new GraphqlApiProps
            {
                Name = "berv-api",
                Schema = Schema.FromAsset("schema.graphql"),
                XrayEnabled = true,
            });

            // Dynamo DB
            var experienceTable = new Table(this, "berv-experiences", new TableProps
            {
                BillingMode = BillingMode.PAY_PER_REQUEST,
                PartitionKey = new Attribute
                {
                    Name = "id",
                    Type = AttributeType.STRING
                },
            });

            // IAM Role
            var role = new Role(this, "Role", new RoleProps
            {
                AssumedBy = new ServicePrincipal("lambda.amazonaws.com")
            });

            // AppSync apply IAM to some mutations
            api.GrantMutation(role, "addExperience");
            api.GrantMutation(role, "updateExperience");
            api.GrantMutation(role, "deleteExperience");

            // Add Dynamo DB as DataSource of AppSync
            var dataSource = api.AddDynamoDbDataSource("experience", experienceTable);
            
            // Add Resolver for Get All Experiences
            dataSource.CreateResolver(new BaseResolverProps
            {
                TypeName = "Query",
                FieldName = "getExperiences",
                RequestMappingTemplate = MappingTemplate.DynamoDbScanTable(),
                ResponseMappingTemplate = MappingTemplate.DynamoDbResultList(),
            });

            // Add Resolver for Get Experience by Id
            dataSource.CreateResolver(new BaseResolverProps
            {
                TypeName = "Query",
                FieldName = "getExperienceById",
                RequestMappingTemplate = MappingTemplate.DynamoDbGetItem("id", "experienceId"),
                ResponseMappingTemplate = MappingTemplate.DynamoDbResultItem(),
            });

            // Add Resolver for Create Experience
            dataSource.CreateResolver(new BaseResolverProps
            {
                TypeName = "Mutation",
                FieldName = "addExperience",
                RequestMappingTemplate =
                    MappingTemplate.DynamoDbPutItem(PrimaryKey.Partition("id").Auto(), Values.Projecting("input")),
                ResponseMappingTemplate = MappingTemplate.DynamoDbResultItem()
            });

            // Add Resolver for Update an Experience
            dataSource.CreateResolver(new BaseResolverProps
            {
                TypeName = "Mutation",
                FieldName = "updateExperience",
                RequestMappingTemplate = MappingTemplate.FromString("""
                { 
                    "version" : "2017-02-28",
                    "operation" : "PutItem",
                    "key": {
                        "id" : $util.dynamodb.toDynamoDBJson($ctx.args.input.id),
                    },
                    "attributeValues" : {
                        "title" : $util.dynamodb.toDynamoDBJson($ctx.args.input.title)
                    }
                }
                """),
                ResponseMappingTemplate = MappingTemplate.DynamoDbResultItem()
            });

            // Add Resolver for Delete an Experience
            dataSource.CreateResolver(new BaseResolverProps
            {
                TypeName = "Mutation",
                FieldName = "deleteExperience",
                RequestMappingTemplate = MappingTemplate.DynamoDbDeleteItem("id", "experienceId"),
                ResponseMappingTemplate = MappingTemplate.FromString("$util.toJson($ctx.result)")
            });
        }
    }
}
```

I didn't have enough information on the best way to map the Update Request. Please feel free to give feedback about it. Anyway, visit this repository for the complete project.

{% github bervProject/my-personal-web-api-infra %}

## Deploying and Testing

* You can deploy the app using `cdk deploy`. Please make sure you've set up the account in your environment. If you're not sure, please check [here](https://docs.aws.amazon.com/cdk/v2/guide/cli.html#cli-environment).
* Let's test our AppSync using in-browser tools provided by AppSync. You can click `Run a query` button.

![Run a query](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rb3kax5zhb5m253yf9sz.png)

* Test Create

![Create](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ildrc5hpueas87s34932.png)

* Test Update

![Update](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o2crt15t5rxushjq2n8w.png)

* Test Get Experiences

![Get Experiences](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zu18nsxpdtz95m3jqrc6.png)

* Test Get Experience by Id

![Get Experience by Id](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ehx4ffmoi16fq5qoot7f.png)

* Test Delete

![Delete](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/trlbxxt8ju70qg1oet4t.png)


## Thank you

![Thanks GIF](https://media.giphy.com/media/KB8C86UMgLDThpt4WT/giphy.gif)