Advanced Resolvers

We designed our schema, created resources, attached resolvers, tested mutations + queries and everything seems to be working fine.

But everything separated.

To find all Users we run the users query, to find all Lists we run the lists query and to find all Items, we run the items query.

Wouldn't it be better if we can ask for items within the lists query itself or ask it lists from within the user query itself?

Yes we can, that's the beauty of GraphQL. Let's do that.


Updating our Schema

As always open the API we created in AWS AppSync, In there go to Schema page and make the following changes.

Let's update our type List to include items field.

type List {
  ...
  items: [Item]
}

Let's update our type User to include lists field.

type User {
  ...
  lists: [List]
}

And while we're at it, Let's update item as well as List type so we can fetch additional info if required.

type Item {
  ...
  list: List
}
type List {
  ...
  user: User
}

This will allow us to find list details from within the item query and Allow us to find user details from within the list query.

This is our final Schema code

type Item {
  # id of the item
  id: ID!
  # id of the list to which this item belongs to
  list_id: ID!
  # name of the item
  name: String!
  # description of the item
  description: String
  # due date of this item
  date: String
  # whether this item has been marked as done or not.
  status: Boolean!
  list: List
}

type List {
  # id of the list
  id: ID!
  # id of the user to whom this list belongs to
  user_id: ID!
  # name of the list
  name: String!
  # description of the list
  description: String
  # all the items associated with this list
  items: [Item]
  user: User
}

type Mutation {
  # this will add a new user in database
  createUser(
    name: String!,
    email: String!,
    password: String!,
    phone: String,
    address: String
  ): User
  # this will update existing user in the database
  updateUser(
    id: ID!,
    name: String,
    email: String,
    password: String,
    phone: String,
    address: String
  ): User
  # this will add a new list in database
  createList(user_id: ID!, name: String!, description: String): List
  # this will update existing list
  updateList(id: ID!, name: String!, description: String): List
  # this will delete a list and all of its items
  deleteList(id: ID!): List
  # this will add an item in the database
  createItem(
    list_id: ID!,
    name: String!,
    description: String,
    date: String,
    completed: Boolean
  ): Item
  # this will update the item in database
  updateItem(
    id: ID!,
    list_id: ID,
    name: String,
    description: String,
    date: String,
    completed: Boolean
  ): Item
  # this will delete the item from database
  deleteItem(id: ID!): Item
  #Put a single value of type 'User'.
  ########## If an item exists it's updated. If it does not it's created.
  putUser(
    id: ID!,
    name: String!,
    email: String!,
    phone: String,
    address: String
  ): User
  #Delete a single value of type 'User' by a primary key.
  deleteUser(id: ID!): User
  #Put a single value of type 'List'.
  ###### If an item exists it's updated. If it does not it's created.
  putList(
    id: ID!,
    user_id: ID!,
    name: String!,
    description: String
  ): List
  #Put a single value of type 'Item'.
  ###### If an item exists it's updated. If it does not it's created.
  putItem(
    id: ID!,
    list_id: ID!,
    name: String!,
    description: String,
    date: String,
    status: Boolean!
  ): Item
}

type Query {
  users: [User]
  user(id: ID!): User
  lists: [List]
  list(id: ID!): List
  items: [Item]
  item(id: ID!): Item
  #Get a single value of type 'User' by primary key.
  getUser(id: ID!): User
  #Scan through all values of type 'User'.
  ########## Use the 'count' and 'nextToken' arguments to paginate.
  allUser(count: Int, nextToken: String): [User]
  #Get a single value of type 'List' by primary key.
  getList(id: ID!): List
  #Scan through all values of type 'List'.
  ###### Use the 'count' and 'nextToken' arguments to paginate.
  allList(count: Int, nextToken: String): [List]
  #Get a single value of type 'Item' by primary key.
  getItem(id: ID!): Item
  #Scan through all values of type 'Item'.
  ###### Use the 'count' and 'nextToken' arguments to paginate.
  allItem(count: Int, nextToken: String): [Item]
}

type User {
  # id of the user
  id: ID!
  # name of the user
  name: String!
  # email of the user
  email: String!
  # phone number of the user
  phone: String
  # address of the user
  address: String
  # all the lists associated with this user.
  lists: [List]
}

schema {
  query: Query
  mutation: Mutation
}

Different ways of getting data in DynamoDB

There are many ways in which we can get the data we want, We set the method in the operation field within our request mapping template.

  • GetItem: to find single record
  • PutItem: to insert new record
  • UpdateItem: for updating the record
  • DeleteItem: for deleting record
  • Scan: finding multiple records
  • Query: finding multiple records

I am not highly experienced in DynamoDB, That aside Scan and Query can be used for finding records matching our conditions and filters.

According to their documentation

The Query operation finds items based on primary key values. You can query any table or secondary index that has a composite primary key (a partition key and a sort key).

A Scan operation reads every item in a table or a secondary index. By default, a Scan operation returns all of the data attributes for every item in the table or index. You can use the ProjectionExpression parameter so that Scan only returns some of the attributes, rather than all of them.

There are many other factors that will come in play when choosing one over other like Speed, Cost, etc. So I will leave this decision upto you.

We will use both of these below so you can see how it's implemented.

To access parents field instead of the arguements we pass via GraphQL, We can make use of "${context.source.FIELD_NAME}"., I don't think this is documented anywhere...


Attaching Resolver for items: [Item] field for Type List

Open Schema page, In the Data Types section search for List, Click on Attach button next to field items: [Item]

For Data source name select ItemTable

For Configure the request mapping template add in this code

{
    "version" : "2017-02-28",
    "operation" : "Scan",
    "filter" : {
        "expression" : "list_id = :list_id",
        "expressionValues" : {
            ":list_id" : { "S" : "${context.source.id}" }
        }
    }
}

What does this code do?

We're saying find all documents where list_id field value is same as id field value of it's source, i..e item.list_id === list.id

And for Configure the response mapping template code, Add this code

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

Attaching Resolver for lists: [List] field for Type User

Open Schema page, In the Data Types section search for User, Click on Attach button next to field lists: [List]

For Data source name select ListTable

For Configure the request mapping template add in this code

{
    "version" : "2017-02-28",
    "operation" : "Query",
    "index": "user_id-index",
    "query" : {
        ## Provide a query expression. **
        "expression": "user_id = :user_id",
        "expressionValues" : {
            ":user_id" : {
                "S" : "${context.source.id}"
            }
        }
    }
}

Here we're using Query operation, If we don't specify the primary key: id It will fail., But we can create a new index on different key and use that.

Here we're just asking the DynamoDB to make use of user_id-index Index. And find all records where user_id field === context.source.id i...e user.id

And for Configure the response mapping template code, Add this code

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

Let's not forget to create new index for field user_id

Open AWS Dynamo DB, Click on Tables link in the left Sidebar.

Once you see all the tables, Select ListTable and it that page goto Indexex tab.

There click on Create index button, It will open a popup, Enter these values for those fields

  • Primary key: Enter user_id
  • Index name: As soon as you enter primary key, It will auto update this field, but you can change the name to whatever you want, I have left it as it is.
  • Read capacity units: I chose 1
  • Write capacity units: I chose 1

Finally create on Create index button, Wait for few seconds and the new index will be added for this table.

AWS DynamoDB - Add New Index

Attaching Resolver for list: List field for Type Item

Open Schema page, In the Data Types section search for Item, Click on Attach button next to field list: List

For Data source name select ListTable

For Configure the request mapping template add in this code

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

And for Configure the response mapping template code, Add this code

$util.toJson($context.result)

Attaching Resolver for user: User field for Type List

Open Schema page, In the Data Types section search for List, Click on Attach button next to field user: User

For Data source name select UserTable

For Configure the request mapping template add in this code

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

And for Configure the response mapping template code, Add this code

$util.toJson($context.result)

And with this we're done.


Testing

Open Queries page and run the following query

query {
  items {
    id
    name
    date
    list {
      id
      name
      description
    }
  }
}

And we get the following response.

{
  "data": {
    "items": [
      {
        "id": "81dcb364-de5e-43c0-a66b-913dd87b7c83",
        "name": "Awesome GraphQL",
        "date": "2017-12-20",
        "list": {
          "id": "b35a92cf-c627-4831-95f2-2cb9f58594fb",
          "name": "Favourite TechStack",
          "description": "These are my Favorite Tech"
        }
      },
      {
        "id": "f8bafed8-fa57-438b-b8c1-95acaf375998",
        "name": "AWS AppSync",
        "date": "2017-12-20",
        "list": {
          "id": "b35a92cf-c627-4831-95f2-2cb9f58594fb",
          "name": "Favourite TechStack",
          "description": "These are my Favorite Tech"
        }
      },
      {
        "id": "aae71b55-22f0-4cd5-bfc6-da870bdb5544",
        "name": "React JS",
        "date": "2017-12-20",
        "list": {
          "id": "b35a92cf-c627-4831-95f2-2cb9f58594fb",
          "name": "Favourite TechStack",
          "description": "These are my Favorite Tech"
        }
      }
    ]
  }
}

This is good, Now let's run an advanced Query, This will be very useful to us as we can get all the data we need in a single request.

query {
  user(id: "5b0fc184-f11d-4342-ab4f-95721e9ee831") {
    id
    name
    email
    lists {
      id
      user_id
      name
      description
      items {
        id
        name
        description
      }
    }
  }
}

And again we get the correct data.

{
  "data": {
    "user": {
      "id": "5b0fc184-f11d-4342-ab4f-95721e9ee831",
      "name": "Dhruv",
      "email": "dhruv@internet.com",
      "lists": [
        {
          "id": "b35a92cf-c627-4831-95f2-2cb9f58594fb",
          "user_id": "5b0fc184-f11d-4342-ab4f-95721e9ee831",
          "name": "Favourite TechStack",
          "description": "These are my Favorite Tech",
          "items": [
            {
              "id": "81dcb364-de5e-43c0-a66b-913dd87b7c83",
              "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",
              "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"
            }
          ]
        },
        {
          "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",
          "items": []
        },
        {
          "id": "319226f5-9600-46b9-9dea-f9c80e4c5db7",
          "user_id": "5b0fc184-f11d-4342-ab4f-95721e9ee831",
          "name": "Favourite Websites",
          "description": "These are my Favorite Websites",
          "items": []
        }
      ]
    }
  }
}

Now let's just get crazy with our Query

query {
  users {
    id
    name
    email
    lists {
      id
      user_id
      name
      description
      user {
        id
        name
        email
        lists {
          id
          name
          description
        }
      }
      items {
        id
        name
        description
        list {
          id
          name
          description
          user {
            id
            name
          }
        }
      }
    }
  }
}

It still works, Awesome.

{
  "data": {
    "users": [
      {
        "id": "5b0fc184-f11d-4342-ab4f-95721e9ee831",
        "name": "Dhruv",
        "email": "dhruv@internet.com",
        "lists": [
          {
            "id": "b35a92cf-c627-4831-95f2-2cb9f58594fb",
            "user_id": "5b0fc184-f11d-4342-ab4f-95721e9ee831",
            "name": "Favourite TechStack",
            "description": "These are my Favorite Tech",
            "user": {
              "id": "5b0fc184-f11d-4342-ab4f-95721e9ee831",
              "name": "Dhruv",
              "email": "dhruv@internet.com",
              "lists": [
                {
                  "id": "b35a92cf-c627-4831-95f2-2cb9f58594fb",
                  "name": "Favourite TechStack",
                  "description": "These are my Favorite Tech"
                },
                {
                  "id": "bee5f479-e875-4090-8de4-704f0ba746c1",
                  "name": "Awesome 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"
                }
              ]
            },
            "items": [
              {
                "id": "81dcb364-de5e-43c0-a66b-913dd87b7c83",
                "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.",
                "list": {
                  "id": "b35a92cf-c627-4831-95f2-2cb9f58594fb",
                  "name": "Favourite TechStack",
                  "description": "These are my Favorite Tech",
                  "user": {
                    "id": "5b0fc184-f11d-4342-ab4f-95721e9ee831",
                    "name": "Dhruv"
                  }
                }
              },
              {
                "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.",
                "list": {
                  "id": "b35a92cf-c627-4831-95f2-2cb9f58594fb",
                  "name": "Favourite TechStack",
                  "description": "These are my Favorite Tech",
                  "user": {
                    "id": "5b0fc184-f11d-4342-ab4f-95721e9ee831",
                    "name": "Dhruv"
                  }
                }
              },
              {
                "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",
                "list": {
                  "id": "b35a92cf-c627-4831-95f2-2cb9f58594fb",
                  "name": "Favourite TechStack",
                  "description": "These are my Favorite Tech",
                  "user": {
                    "id": "5b0fc184-f11d-4342-ab4f-95721e9ee831",
                    "name": "Dhruv"
                  }
                }
              }
            ]
          },
          {
            "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",
            "user": {
              "id": "5b0fc184-f11d-4342-ab4f-95721e9ee831",
              "name": "Dhruv",
              "email": "dhruv@internet.com",
              "lists": [
                {
                  "id": "b35a92cf-c627-4831-95f2-2cb9f58594fb",
                  "name": "Favourite TechStack",
                  "description": "These are my Favorite Tech"
                },
                {
                  "id": "bee5f479-e875-4090-8de4-704f0ba746c1",
                  "name": "Awesome 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"
                }
              ]
            },
            "items": []
          },
          {
            "id": "319226f5-9600-46b9-9dea-f9c80e4c5db7",
            "user_id": "5b0fc184-f11d-4342-ab4f-95721e9ee831",
            "name": "Favourite Websites",
            "description": "These are my Favorite Websites",
            "user": {
              "id": "5b0fc184-f11d-4342-ab4f-95721e9ee831",
              "name": "Dhruv",
              "email": "dhruv@internet.com",
              "lists": [
                {
                  "id": "b35a92cf-c627-4831-95f2-2cb9f58594fb",
                  "name": "Favourite TechStack",
                  "description": "These are my Favorite Tech"
                },
                {
                  "id": "bee5f479-e875-4090-8de4-704f0ba746c1",
                  "name": "Awesome 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"
                }
              ]
            },
            "items": []
          }
        ]
      },
      {
        "id": "f20057e6-0a78-4abb-b6c2-9f3d29d83d90",
        "name": "John Doe",
        "email": "john.doe@gmail.com",
        "lists": [
          {
            "id": "656e99e5-0ce6-4f5e-b59b-6c6c0b4405f4",
            "user_id": "f20057e6-0a78-4abb-b6c2-9f3d29d83d90",
            "name": "John Doe's List",
            "description": "My awesome list of random things.",
            "user": {
              "id": "f20057e6-0a78-4abb-b6c2-9f3d29d83d90",
              "name": "John Doe",
              "email": "john.doe@gmail.com",
              "lists": [
                {
                  "id": "656e99e5-0ce6-4f5e-b59b-6c6c0b4405f4",
                  "name": "John Doe's List",
                  "description": "My awesome list of random things."
                }
              ]
            },
            "items": []
          }
        ]
      }
    ]
  }
}

Testing with HTTP Clients:

Up until this point we've used AppSync Queries page to test our queries. Let's get our of AWS and test our queries with HTTP clients.

You can use any HTTP client, I will be using PostMan to test our query.

Do note we cannot use of GraphQL queries as it with these clients, We have to escape the quotes and new lines.

In AWS AppSync click on your API. This will open up your API details page.

Here we can see the API URL: https://icemqn5opzewdeer2s2isyg5su.appsync-api.us-east-1.amazonaws.com/graphql and API KEY: da1-abcdefghijklmnopsdhjdshjkf (I have changed my API key so this wont work.)

Whichever client you choose, Select

In Headers, Add new fields

  • x-api-key: da1-abcdefghijklmnopsdhjdshjkf
  • Content-Type: application/json

In body, Paste in this RAW json (this is our query)

{
  "query": "query { user(id: \"5b0fc184-f11d-4342-ab4f-95721e9ee831\") { id name email lists { id user_id name description items { id name description } } } }"
}

Hit Send to make the HTTPS Request and we get the response

{
    "data": {
        "user": {
            "id": "5b0fc184-f11d-4342-ab4f-95721e9ee831",
            "name": "Dhruv",
            "email": "dhruv@internet.com",
            "lists": [
                {
                    "id": "b35a92cf-c627-4831-95f2-2cb9f58594fb",
                    "user_id": "5b0fc184-f11d-4342-ab4f-95721e9ee831",
                    "name": "Favourite TechStack",
                    "description": "These are my Favorite Tech",
                    "items": [
                        {
                            "id": "81dcb364-de5e-43c0-a66b-913dd87b7c83",
                            "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",
                            "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"
                        }
                    ]
                },
                {
                    "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",
                    "items": []
                },
                {
                    "id": "319226f5-9600-46b9-9dea-f9c80e4c5db7",
                    "user_id": "5b0fc184-f11d-4342-ab4f-95721e9ee831",
                    "name": "Favourite Websites",
                    "description": "These are my Favorite Websites",
                    "items": []
                }
            ]
        }
    }
}

If you encountered any errors/issues, Just let me know.

HTTP Query Response

Conclusion

With this we're done (with the backend).

You can use the same API URL and API Key within React Project to communicate with the GraphQL Backend., And that's exactly what we will do next.

Meta Information

This article was published on December 21, 2017 at 02:30 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