DEV Community

Cover image for ๐Ÿคนโ€โ™‚ AWS CDK 101 - ๐Ÿ„ GraphQL Mutations using AppSync with dynamodb
Aravind V
Aravind V

Posted on • Originally published at devpost.hashnode.dev

๐Ÿคนโ€โ™‚ AWS CDK 101 - ๐Ÿ„ GraphQL Mutations using AppSync with dynamodb

๐Ÿ”ฐ Beginners new to AWS CDK, please do look at my previous articles one by one in this series.

If in case missed my previous article, do find it with the below links.

๐Ÿ” Original previous post at ๐Ÿ”— Dev Post

๐Ÿ” Reposted the previous post at ๐Ÿ”— dev to @aravindvcyber

In this article, we will be introducing a data access layer as a wrapper on top of our dynamodb table. Here specifically we have chosen graphql using AWS appsync to perform mutations in our dynamodb table.

Construction ๐Ÿ—๏ธ

Let us start by updating our previous file lib/appsync-stack.ts for our new stack.

gql front

Schema definition for Mutations ๐ŸŽข

You may also use the AWS console to update the schema before we update in CDK asset files when we know for sure.

Here you can find the mutations part which we have appended to our schema file which we created in our previous article above.

input CreateMessagesTableInput {
    createdAt: Int!
    messageId: String!
    event: AWSJSON
}

input UpdateMessagesTableInput {
    createdAt: Int!
    messageId: String!
    event: AWSJSON
}

input DeleteMessagesTableInput {
    createdAt: Int!
    messageId: String!
}

type Mutation {
    createMessagesTable(input: CreateMessagesTableInput!): MessagesTable
    updateMessagesTable(input: UpdateMessagesTableInput!): MessagesTable
    deleteMessagesTable(input: DeleteMessagesTableInput!): MessagesTable
}
Enter fullscreen mode Exit fullscreen mode

schema update

Dynamodb connection as a data source ๐Ÿ›ถ

Here we have already directly integrated dynamodb API with graphql as a data source.

Additionally we are updating our earlier article by making use of the import value of table arn to get the table name directly from the previous stack exports.

Exporting table arn from another stack ๐Ÿ†

new CfnOutput(this, "MessagesTableExport", {
      value: messages.tableArn,
      exportName: 'MessagesTableArn'
    })
Enter fullscreen mode Exit fullscreen mode

Importing table arn in this stack ๐Ÿฎ

const messages = Table.fromTableArn(
      this,
      "MessagesTableImport",
      Fn.importValue("MessagesTableArn")
    );

const MessagesDS = AppSyncApi.addDynamoDbDataSource("MessagesDataSource", messages);
Enter fullscreen mode Exit fullscreen mode

GQL visual

VTL Mapping template ๐Ÿ›ฉ๏ธ

Here we need to use VTL (Velocity Template Language) to transform/manipulate our request and the response we send/receive from the below resolvers. Using this can be a good strategy as this can be used in many places not only in appsync and API gateway.

You may also use the AWS console to test these transformations using the sample payload from logs before we update in CDK asset files.

create test

test result

mutation resolvers

Create message resolver ๐Ÿš 

Here you can also look into the Xray trace to understand when these blocks are utilized for createMessagesTable resolver

MessagesDS.createResolver({
      typeName: "Mutation",
      fieldName: "createMessagesTable",
      requestMappingTemplate: MappingTemplate.fromFile(
        "assets/createMessagesTableRequest.vtl"
      ),
      responseMappingTemplate: MappingTemplate.fromFile(
        "assets/createMessagesTableResponse.vtl"
      ),
    });
Enter fullscreen mode Exit fullscreen mode

createMessagesTableRequest VTL template ๐ŸŒŸ

{
  "version": "2017-02-28",
  "operation": "PutItem",
  "key": {
    "messageId": $util.dynamodb.toDynamoDBJson($ctx.args.input.messageId),
    "createdAt": $util.dynamodb.toDynamoDBJson($ctx.args.input.createdAt),
  },
  "attributeValues": $util.dynamodb.toMapValuesJson($ctx.args.input),
  "condition": {
    "expression": "attribute_not_exists(#messageId) AND attribute_not_exists(#createdAt)",
    "expressionNames": {
      "#messageId": "messageId",
      "#createdAt": "createdAt",
    },
  },
}
Enter fullscreen mode Exit fullscreen mode

createMessagesTableResponse VTL template โ›ฑ๏ธ


$util.toJson($context.result)

Enter fullscreen mode Exit fullscreen mode

create xray

Delete message resolver ๐Ÿคก

Here you can also look into the Xray trace to understand when these blocks are utilized for deleteMessagesTable resolver

MessagesDS.createResolver({
      typeName: "Mutation",
      fieldName: "deleteMessagesTable",
      requestMappingTemplate: MappingTemplate.fromFile(
        "assets/deleteMessagesTableRequest.vtl"
      ),
      responseMappingTemplate: MappingTemplate.fromFile(
        "assets/deleteMessagesTableResponse.vtl"
      ),
    });
Enter fullscreen mode Exit fullscreen mode

deleteMessagesTableRequest VTL template ๐ŸŽˆ

{
  "version": "2017-02-28",
  "operation": "DeleteItem",
  "key": {
    "messageId": $util.dynamodb.toDynamoDBJson($ctx.args.input.messageId),
    "createdAt": $util.dynamodb.toDynamoDBJson($ctx.args.input.createdAt),
  },
}
Enter fullscreen mode Exit fullscreen mode

deleteMessagesTableResponse VTL template ๐ŸŽฃ

$util.toJson($context.result)
Enter fullscreen mode Exit fullscreen mode

delete xray

Update messages resolver ๐Ÿฆ„

Here you can also look into the xray trace to understand when these blocks are utilized for updateMessagesTable resolver

MessagesDS.createResolver({
      typeName: "Mutation",
      fieldName: "updateMessagesTable",
      requestMappingTemplate: MappingTemplate.fromFile(
        "assets/updateMessagesTableRequest.vtl"
      ),
      responseMappingTemplate: MappingTemplate.fromFile(
        "assets/updateMessagesTableResponse.vtl"
      ),
    });
Enter fullscreen mode Exit fullscreen mode

updateMessagesTableRequest VTL template ๐Ÿฟ๏ธ

This update logic may look complex but just remember that while updating we need to take care of updated attributes, removed attributes, and new attributes.

{
  "version": "2017-02-28",
  "operation": "UpdateItem",
  "key": {
    "messageId": $util.dynamodb.toDynamoDBJson($ctx.args.input.messageId),
    "createdAt": $util.dynamodb.toDynamoDBJson($ctx.args.input.createdAt),
  },

  #set( $expNames  = {} )
  #set( $expValues = {} )
  #set( $expSet = {} )
  #set( $expAdd = {} )
  #set( $expRemove = [] )

  ## Looping through each argument, except keys **
  #foreach( $entry in $util.map.copyAndRemoveAllKeys($ctx.args.input, ["messageId", "createdAt"]).entrySet() )
    #if( $util.isNull($entry.value) )
      #set( $discard = ${expRemove.add("#${entry.key}")} )
      $!{expNames.put("#${entry.key}", "${entry.key}")}
    #else
      $!{expSet.put("#${entry.key}", ":${entry.key}")}
      $!{expNames.put("#${entry.key}", "${entry.key}")}
      $!{expValues.put(":${entry.key}", $util.dynamodb.toDynamoDB($entry.value))}
    #end
  #end

## Updating existing attributes **
  #set( $expression = "" )
  #if( !${expSet.isEmpty()} )
    #set( $expression = "SET" )
    #foreach( $entry in $expSet.entrySet() )
      #set( $expression = "${expression} ${entry.key} = ${entry.value}" )
      #if ( $foreach.hasNext )
        #set( $expression = "${expression}," )
      #end
    #end
  #end

  ## Adding new attributes **
  #if( !${expAdd.isEmpty()} )
    #set( $expression = "${expression} ADD" )
    #foreach( $entry in $expAdd.entrySet() )
      #set( $expression = "${expression} ${entry.key} ${entry.value}" )
      #if ( $foreach.hasNext )
        #set( $expression = "${expression}," )
      #end
    #end
  #end

  ## Removing unwanted attributes **
  #if( !${expRemove.isEmpty()} )
    #set( $expression = "${expression} REMOVE" )

    #foreach( $entry in $expRemove )
      #set( $expression = "${expression} ${entry}" )
      #if ( $foreach.hasNext )
        #set( $expression = "${expression}," )
      #end
    #end
  #end

  ## Final update expression **
  "update": {
    "expression": "${expression}",
    #if( !${expNames.isEmpty()} )
      "expressionNames": $utils.toJson($expNames),
    #end
    #if( !${expValues.isEmpty()} )
      "expressionValues": $utils.toJson($expValues),
    #end
  },

  "condition": {
    "expression": "attribute_exists(#messageId) AND attribute_exists(#createdAt)",
    "expressionNames": {
      "#messageId": "messageId",
      "#createdAt": "createdAt",
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

updateMessagesTableResponse VTL template ๐Ÿ

$util.toJson($context.result)
Enter fullscreen mode Exit fullscreen mode

update xray

Client to explore graphQl โ„๏ธ

client

Appsync Explorer Queries โ™จ๏ธ

In the AWS console, you can navigate the appsync and start querying. One advantage you have here is that we have cloud watch and tracing logs readily available if in case you want to check.

create

update

delete

Apollo Studio Queries ๐Ÿ•๏ธ

But normally prefer the dark mode in apollo graphql studio, you may also try it out if you prefer that, maybe we would get that in the AWS console as well someday.

collection

create apollo

update apollo

del apollo

We will be refining this in our coming articles to achieve optimum speed and resource utilization.

We will be adding more connections to our stack and making it more usable in the upcoming articles by creating new constructs, so do consider following and subscribing to my newsletter.

โญ We have our next article in serverless, do check out

๐ŸŽ‰ Thanks for supporting! ๐Ÿ™

Would be great if you like to โ˜• Buy Me a Coffee, to help boost my efforts ๐Ÿ˜.

Buy Me a Coffee at ko-fi.com

๐Ÿ” Original post at ๐Ÿ”— Dev Post

๐Ÿ” Reposted at ๐Ÿ”— dev to @aravindvcyber


๐Ÿคนโ€โ™‚ AWS CDK 101 - ๐Ÿ„ GraphQL Mutations using AppSync with dynamodb

Checkout more in my pagehttps://t.co/CuYxnKr0Ig#Serverless#typescript #graphql #awscdk #dynamodb #aws https://t.co/3y1zUMIyFk

โ€” Aravind V (@Aravind_V7) June 4, 2022

Discussion (0)