Creating Additional Resources and Attaching Resolvers to Rest of Our Mutations and Queries

Let's recap, This is what we have done till now

  • Designed our Schema
  • Created Resource: UserTable (User Type)
  • Attached resolver for createUser mutation
  • Attached resolver for users Query.
  • Attached resolver for updateUser mutation
  • Attached resolver for deleteUser mutation

And this is what we have left to do?

  • Attach resolver for user Query
  • Create Resource: ListTable (List Type)
  • Create Resource: ItemTable (Item Type)

  • Attach resolver for createList mutation
  • Attach resolver for updateList mutation
  • Attach resolver for deleteList mutation
  • Attach resolver for lists Query
  • Attach resolver for list Query

  • Attach resolver for createItem mutation
  • Attach resolver for updateItem mutation
  • Attach resolver for deleteItem mutation
  • Attach resolver for items Query
  • Attach resolver for item Query

We have a lot to do., I mean we have lot of repetitive work to do.

We have already covered these in previous tutorials so I won't be explaining these in details, If you're new just look at previous tutorials.

Let's Begin.


Linking user Query to the Resolver:

In Schema page, Click on Attach button next to field user: User, Select UserTable as Data source name.

In Configure the request mapping template paste in the below code.

{
    "version": "2017-02-28",
    "operation": "GetItem",
    "key": {
        "id": { "S": "$context.arguments.id" }
    }
}

For Configure the response mapping template enter this code

$util.toJson($context.result)

And hit that Save Button., That's it we're done with this.

Let's test this by running the query (f20057e6-0a78-4abb-b6c2-9f3d29d83d90 is the id of user from the database, right now we only have one user.)

query {
  user(id: "f20057e6-0a78-4abb-b6c2-9f3d29d83d90") {
    id
    name
    email
    phone
    address
  }
}

After we execute the query, We see this response.

{
  "data": {
    "user": {
      "id": "f20057e6-0a78-4abb-b6c2-9f3d29d83d90",
      "name": "John Doe",
      "email": "john.doe@gmail.com",
      "phone": null,
      "address": "Bangalore"
    }
  }
}

We successfully attached resolver to our query.


Creating Resources for List Type and Item Type

In Schema page, Click on Create Resources button, It will open Create Resources page.

In there for Select a type field select List. As soon as you select in new fields will appear.

For Create a table to hold values for the List type filed you can see it prefilled it with ListTable which is fine.

For Configure the ListTable DynamoDB table section, Leave everything as is.

For We will add the following to your schema field, We don't need it but there's nothing we can do as AppSync will add it irrespective of our usage.

Now click on that Create Button. AppSync will create the required table for us.

Repeat the same process for Item type.

For Create a table to hold values for the Item type make sure its ItemTable

And we have created tables for both of our List and Item Types.


Linking createList, updateList, deleteList Mutations to the Resolver:

Open Schema page, Click on Attach button next to createList(...): List (in Data Types section)

In Create new resolver page for Data source name select ListTable

For Configure the request mapping template Field, Paste the below code

{
    "version" : "2017-02-28",
    "operation" : "PutItem",
    "key" : {
        ## If your table's hash key is not named 'id', update it here. **
        "id": { "S": "$util.autoId()"}
        ## If your table has a sort key, add it as an item here. **
    },

  #set( $expValues = {} )

    #foreach( $entry in $context.arguments.entrySet() )
    $!{expValues.put("${entry.key}", { "S" : "${entry.value}" })}
    #end

    #if( !${expValues.isEmpty()} )
    "attributeValues" : $utils.toJson($expValues)
    #end
}

For Configure the response mapping template field, Paste this code

$util.toJson($context.result)

And click on the Save button.

For updateList(...): List

Repeat the same steps, But for the field Configure the request mapping template Use this code

{
    "version" : "2017-02-28",
    "operation" : "UpdateItem",
    "key" : {
        "id" : { "S" : "${context.arguments.id}" }
    },

    ## Set up some space to keep track of things we're updating **
    #set( $expNames  = {} )
    #set( $expValues = {} )
    #set( $expSet = {} )
    #set( $expAdd = {} )
    #set( $expRemove = [] )

    ## Iterate through each argument, skipping "id" and "expectedVersion" **
    #foreach( $entry in $context.arguments.entrySet() )
        #if( $entry.key != "id" )
            #if( (!$entry.value) && ("$!{entry.value}" == "") )
                ## If the argument is set to "null", then remove that attribute from the item in DynamoDB **

                #set( $discard = ${expRemove.add("#${entry.key}")} )
                $!{expNames.put("#${entry.key}", "$entry.key")}
            #else
                ## Otherwise set (or update) the attribute on the item in DynamoDB **

                $!{expSet.put("#${entry.key}", ":${entry.key}")}
                $!{expNames.put("#${entry.key}", "$entry.key")}
                $!{expValues.put(":${entry.key}", { "S" : "${entry.value}" })}
            #end
        #end
    #end

    ## Start building the update expression, starting with attributes we're going to SET **
    #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

    ## Continue building the update expression, adding attributes we're going to ADD **
    #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

    ## Continue building the update expression, adding attributes we're going to REMOVE **
    #if( !${expRemove.isEmpty()} )
        #set( $expression = "${expression} REMOVE" )

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

    ## Finally, write the update expression into the document, along with any expressionNames and expressionValues **
    "update" : {
        "expression" : "${expression}"
        #if( !${expNames.isEmpty()} )
            ,"expressionNames" : $utils.toJson($expNames)
        #end
        #if( !${expValues.isEmpty()} )
            ,"expressionValues" : $utils.toJson($expValues)
        #end
    }
}

For deleteList(...): List

Do Nothing, As this was already created by AppSync when we created the resources.


Linking lists and list Queries to the Resolver:

Click on Attach button next to lists: [List] and for Data source name select ListTable.

For Configure the request mapping template field enter the below code

{
    "version" : "2017-02-28",
    "operation" : "Scan",
    ## Add 'limit' and 'nextToken' arguments to this field in your schema to implement pagination. **
    "limit": #if(${context.arguments.limit}) ${context.arguments.limit} #else 20 #end,
    "nextToken": #if(${context.arguments.nextToken}) "${context.arguments.nextToken}" #else null #end
}

For Configure the response mapping template field, Enter this code

$utils.toJson($context.result.items)

And then click on the Save button.

No do the same for list(...): List Query, but for field Configure the request mapping template enter this code

{
    "version": "2017-02-28",
    "operation": "GetItem",
    "key": {
        "id": { "S": "$context.arguments.id" }
    }
}

And for field Configure the response mapping template

$util.toJson($context.result)

And we have successfully added resolvers for our queries.


Linking createItem, updateItem, deleteItem Mutations to the Resolver:

Open Schema page, Click on Attach button next to createItem(...): Item (in Data Types section)

In Create new resolver page for Data source name select ItemTable

For Configure the request mapping template Field, Paste the below code

{
    "version" : "2017-02-28",
    "operation" : "PutItem",
    "key" : {
        ## If your table's hash key is not named 'id', update it here. **
        "id": { "S": "$util.autoId()"}
        ## If your table has a sort key, add it as an item here. **
    },

  #set( $expValues = {} )

    #foreach( $entry in $context.arguments.entrySet() )
    $!{expValues.put("${entry.key}", { "S" : "${entry.value}" })}
    #end

    #if( !${expValues.isEmpty()} )
    "attributeValues" : $utils.toJson($expValues)
    #end
}

For Configure the response mapping template field, Paste this code

$util.toJson($context.result)

And click on the Save button.

For updateItem(...): Item

Repeat the same steps, But for the field Configure the request mapping template Use this code

{
    "version" : "2017-02-28",
    "operation" : "UpdateItem",
    "key" : {
        "id" : { "S" : "${context.arguments.id}" }
    },

    ## Set up some space to keep track of things we're updating **
    #set( $expNames  = {} )
    #set( $expValues = {} )
    #set( $expSet = {} )
    #set( $expAdd = {} )
    #set( $expRemove = [] )

    ## Iterate through each argument, skipping "id" and "expectedVersion" **
    #foreach( $entry in $context.arguments.entrySet() )
        #if( $entry.key != "id" )
            #if( (!$entry.value) && ("$!{entry.value}" == "") )
                ## If the argument is set to "null", then remove that attribute from the item in DynamoDB **

                #set( $discard = ${expRemove.add("#${entry.key}")} )
                $!{expNames.put("#${entry.key}", "$entry.key")}
            #else
                ## Otherwise set (or update) the attribute on the item in DynamoDB **

                $!{expSet.put("#${entry.key}", ":${entry.key}")}
                $!{expNames.put("#${entry.key}", "$entry.key")}
                $!{expValues.put(":${entry.key}", { "S" : "${entry.value}" })}
            #end
        #end
    #end

    ## Start building the update expression, starting with attributes we're going to SET **
    #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

    ## Continue building the update expression, adding attributes we're going to ADD **
    #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

    ## Continue building the update expression, adding attributes we're going to REMOVE **
    #if( !${expRemove.isEmpty()} )
        #set( $expression = "${expression} REMOVE" )

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

    ## Finally, write the update expression into the document, along with any expressionNames and expressionValues **
    "update" : {
        "expression" : "${expression}"
        #if( !${expNames.isEmpty()} )
            ,"expressionNames" : $utils.toJson($expNames)
        #end
        #if( !${expValues.isEmpty()} )
            ,"expressionValues" : $utils.toJson($expValues)
        #end
    }
}

For deleteItem(...): Item

Do Nothing, As this was already created by AppSync when we created the resources.


Linking items item list Queries to the Resolver:

Click on Attach button next to items: [Item] and for Data source name select ItemTable.

For Configure the request mapping template field enter the below code

{
    "version" : "2017-02-28",
    "operation" : "Scan",
    ## Add 'limit' and 'nextToken' arguments to this field in your schema to implement pagination. **
    "limit": #if(${context.arguments.limit}) ${context.arguments.limit} #else 20 #end,
    "nextToken": #if(${context.arguments.nextToken}) "${context.arguments.nextToken}" #else null #end
}

For Configure the response mapping template field, Enter this code

$utils.toJson($context.result.items)

And then click on the Save button.

No do the same for item(...): Item Query, but for field Configure the request mapping template enter this code

{
    "version": "2017-02-28",
    "operation": "GetItem",
    "key": {
        "id": { "S": "$context.arguments.id" }
    }
}

And for field Configure the response mapping template

$util.toJson($context.result)

We have successfully added resolvers for our queries.

And with this we're done Attaching resolvers to our Queries and Mutations.


Testing

Let's test everything we did by running these Mutations and Queries.

Let's start by adding new user with this Query

mutation {
  createUser(
    name: "Dhruv"
    email: "dhruv@internet.com"
    password: "pass123"
  ) {
    id
    name
    email
  }
}

This will return the response

{
  "data": {
    "createUser": {
      "id": "5b0fc184-f11d-4342-ab4f-95721e9ee831",
      "name": "Dhruv",
      "email": "dhruv@internet.com"
    }
  }
}

Everything we do from this point will be related to this user, List Type needs a user id and Item Type needs a List id.

I made few changes to the Designing our Schema tutorial, be sure to check and update the Schema code from there so you're up to doate.

Let's create few Lists for our user with id 5b0fc184-f11d-4342-ab4f-95721e9ee831 by running this code.

mutation {
  createList(
    user_id: "5b0fc184-f11d-4342-ab4f-95721e9ee831"
    name: "Tutorial Ideas"
    description: "These are some of the tutorial ideas I have"
  ) {
    id
    name
    description
  }
}

And we get the expected response (which is always good)

{
  "data": {
    "createList": {
      "id": "bee5f479-e875-4090-8de4-704f0ba746c1",
      "name": "Tutorial Ideas",
      "description": "These are some of the tutorial ideas I have"
    }
  }
}

Let's run few more mutations

mutation FavList{
  createList(
    user_id: "5b0fc184-f11d-4342-ab4f-95721e9ee831"
    name: "Favourite Websites"
    description: "These are my Favorite Websites"
  ) {
    id
    name
    description
  }
}
mutation TechStack{
  createList(
    user_id: "5b0fc184-f11d-4342-ab4f-95721e9ee831"
    name: "Favourite TechStack"
    description: "These are my Favorite Tech"
  ) {
    id
    name
    description
  }
}

Now if we run

query {
  lists {
    id
    name
    description
  }
}

We see the following response

{
  "data": {
    "lists": [
      {
        "id": "b35a92cf-c627-4831-95f2-2cb9f58594fb",
        "name": "Favourite TechStack",
        "description": "These are my Favorite Tech"
      },
      {
        "id": "bee5f479-e875-4090-8de4-704f0ba746c1",
        "name": "Tutorial Ideas",
        "description": "These are some of the tutorial ideas I have"
      },
      {
        "id": "319226f5-9600-46b9-9dea-f9c80e4c5db7",
        "name": "Favourite Websites",
        "description": "These are my Favorite Websites"
      }
    ]
  }
}

And if we run the query

query {
  list(id: "b35a92cf-c627-4831-95f2-2cb9f58594fb") {
    id
    user_id
    name
    description
  }
}

We get the following response.

{
  "data": {
    "list": {
      "id": "b35a92cf-c627-4831-95f2-2cb9f58594fb",
      "user_id": "5b0fc184-f11d-4342-ab4f-95721e9ee831",
      "name": "Favourite TechStack",
      "description": "These are my Favorite Tech"
    }
  }
}

Let's create few Items for our List with id b35a92cf-c627-4831-95f2-2cb9f58594fb by running this code.

mutation {
  createItem(
    list_id: "b35a92cf-c627-4831-95f2-2cb9f58594fb"
    name: "React JS"
    description: "In computing, React is a JavaScript library for building user interfaces. It is maintained by Facebook, Instagram and a community of individual developers and corporations"
    date: "2017-12-20"
  ) {
    id
    name
    description
    date
  }
}
mutation {
  createItem(
    list_id: "b35a92cf-c627-4831-95f2-2cb9f58594fb"
    name: "GraphQL"
    description: "GraphQL is a data query language developed internally by Facebook in 2012 before being publicly released in 2015. It provides an alternative to REST and ad-hoc webservice architectures."
    date: "2017-12-20"
  ) {
    id
    name
    description
    date
  }
}
mutation {
  createItem(
    list_id: "b35a92cf-c627-4831-95f2-2cb9f58594fb"
    name: "AWS AppSync"
    description: "AWS AppSync automatically updates the data in web and mobile applications in real time, and updates data for offline users as soon as they reconnect. AppSync makes it easy to build collaborative mobile and web applications that deliver responsive, collaborative user experiences."
    date: "2017-12-20"
  ) {
    id
    name
    description
    date
  }
}

Now if we run the query

query {
  items {
    id
    name
    description
  }
}

We get the following response

{
  "data": {
    "items": [
      {
        "id": "81dcb364-de5e-43c0-a66b-913dd87b7c83",
        "name": "GraphQL",
        "description": "GraphQL is a data query language developed internally by Facebook in 2012 before being publicly released in 2015. It provides an alternative to REST and ad-hoc webservice architectures."
      },
      {
        "id": "f8bafed8-fa57-438b-b8c1-95acaf375998",
        "name": "AWS AppSync",
        "description": "AWS AppSync automatically updates the data in web and mobile applications in real time, and updates data for offline users as soon as they reconnect. AppSync makes it easy to build collaborative mobile and web applications that deliver responsive, collaborative user experiences."
      },
      {
        "id": "aae71b55-22f0-4cd5-bfc6-da870bdb5544",
        "name": "React JS",
        "description": "In computing, React is a JavaScript library for building user interfaces. It is maintained by Facebook, Instagram and a community of individual developers and corporations"
      }
    ]
  }
}

And if we run the query

query {
  item(id: "f8bafed8-fa57-438b-b8c1-95acaf375998") {
    id
    name
    description
  }
}

We get the repsonse

{
  "data": {
    "item": {
      "id": "f8bafed8-fa57-438b-b8c1-95acaf375998",
      "name": "AWS AppSync",
      "description": "AWS AppSync automatically updates the data in web and mobile applications in real time, and updates data for offline users as soon as they reconnect. AppSync makes it easy to build collaborative mobile and web applications that deliver responsive, collaborative user experiences."
    }
  }
}

Let's test our Update Mutations by running this query

mutation {
  updateList(
    id:"bee5f479-e875-4090-8de4-704f0ba746c1"
    name:"Awesome Tutorial Ideas"
  ) {
    id
    name
  }
}

We see the response

{
  "data": {
    "updateList": {
      "id": "bee5f479-e875-4090-8de4-704f0ba746c1",
      "name": "Awesome Tutorial Ideas"
    }
  }
}

And if we run the query

mutation {
  updateItem(
    id:"81dcb364-de5e-43c0-a66b-913dd87b7c83"
    name:"Awesome GraphQL"
  ) {
    id
    name
  }

We get the response

{
  "data": {
    "updateItem": {
      "id": "81dcb364-de5e-43c0-a66b-913dd87b7c83",
      "name": "Awesome GraphQL"
    }
  }
}

Testing all queries together

Run the query

query {
  users {
    id
    name
    email
  }
  lists {
    id
    user_id
    name
    description
  }
  items {
    id
    list_id
    name
    description
  }
  user(id: "5b0fc184-f11d-4342-ab4f-95721e9ee831") {
    name
    email
    phone
  }
  list(id: "b35a92cf-c627-4831-95f2-2cb9f58594fb") {
    name
    description
  }
  item(id: "81dcb364-de5e-43c0-a66b-913dd87b7c83") {
    name
    description
  }
}

And we get our response

AWS AppSync - Query Response
{
  "data": {
    "users": [
      {
        "id": "5b0fc184-f11d-4342-ab4f-95721e9ee831",
        "name": "Dhruv",
        "email": "dhruv@internet.com"
      },
      {
        "id": "f20057e6-0a78-4abb-b6c2-9f3d29d83d90",
        "name": "John Doe",
        "email": "john.doe@gmail.com"
      }
    ],
    "lists": [
      {
        "id": "b35a92cf-c627-4831-95f2-2cb9f58594fb",
        "user_id": "5b0fc184-f11d-4342-ab4f-95721e9ee831",
        "name": "Favourite TechStack",
        "description": "These are my Favorite Tech"
      },
      {
        "id": "bee5f479-e875-4090-8de4-704f0ba746c1",
        "user_id": "5b0fc184-f11d-4342-ab4f-95721e9ee831",
        "name": "Awesome Tutorial Ideas",
        "description": "These are some of the tutorial ideas I have"
      },
      {
        "id": "319226f5-9600-46b9-9dea-f9c80e4c5db7",
        "user_id": "5b0fc184-f11d-4342-ab4f-95721e9ee831",
        "name": "Favourite Websites",
        "description": "These are my Favorite Websites"
      }
    ],
    "items": [
      {
        "id": "81dcb364-de5e-43c0-a66b-913dd87b7c83",
        "list_id": "b35a92cf-c627-4831-95f2-2cb9f58594fb",
        "name": "Awesome GraphQL",
        "description": "GraphQL is a data query language developed internally by Facebook in 2012 before being publicly released in 2015. It provides an alternative to REST and ad-hoc webservice architectures."
      },
      {
        "id": "f8bafed8-fa57-438b-b8c1-95acaf375998",
        "list_id": "b35a92cf-c627-4831-95f2-2cb9f58594fb",
        "name": "AWS AppSync",
        "description": "AWS AppSync automatically updates the data in web and mobile applications in real time, and updates data for offline users as soon as they reconnect. AppSync makes it easy to build collaborative mobile and web applications that deliver responsive, collaborative user experiences."
      },
      {
        "id": "aae71b55-22f0-4cd5-bfc6-da870bdb5544",
        "list_id": "b35a92cf-c627-4831-95f2-2cb9f58594fb",
        "name": "React JS",
        "description": "In computing, React is a JavaScript library for building user interfaces. It is maintained by Facebook, Instagram and a community of individual developers and corporations"
      }
    ],
    "user": {
      "name": "Dhruv",
      "email": "dhruv@internet.com",
      "phone": null
    },
    "list": {
      "name": "Favourite TechStack",
      "description": "These are my Favorite Tech"
    },
    "item": {
      "name": "Awesome GraphQL",
      "description": "GraphQL is a data query language developed internally by Facebook in 2012 before being publicly released in 2015. It provides an alternative to REST and ad-hoc webservice architectures."
    }
  }
}

Conclusion

We tested all of our Queries and Mutations and all of them are working.

So are we done?

We're almost done., I might write another Tutorial on this (Backend) or move onto the Frontend

Although in future tutorials we will get back to this and make changes to our Schema as required.

And with this Ladies and Gentlemen We have a Fully functional serverless GraphQL endpoint up and running.

Ofcourse you can't use it unless you're authenticated.., So I guess I have to write another tutorial on this.

Until then, Keep Learning.

Meta Information

This article was published on December 20, 2017 at 02:20 PM and is written by Dhruv Kumar Jha (me).
This is part of series: Building a Todo Application using GraphQL and AWS AppSync
Tags
GraphQL
AWS
AppSync
AWS AppSync