View a markdown version of this page

Creating a simple post application using DynamoDB resolvers - AWS AppSync GraphQL

Creating a simple post application using DynamoDB resolvers

Note

We now primarily support the APPSYNC_JS runtime and its documentation. Please consider using the APPSYNC_JS runtime and its guides here.

This tutorial shows how you can bring your own Amazon DynamoDB tables to AWS AppSync and connect them to a GraphQL API.

You can let AWS AppSync provision DynamoDB resources on your behalf. Or, if you prefer, you can connect your existing tables to a GraphQL schema by creating a data source and a resolver. In either case, you’ll be able to read and write to your DynamoDB database through GraphQL statements and subscribe to real-time data.

There are specific configuration steps that need to be completed in order for GraphQL statements to be translated to DynamoDB operations, and for responses to be translated back into GraphQL. This tutorial outlines the configuration process through several real-world scenarios and data access patterns.

Setting up your DynamoDB tables

To begin this tutorial, first you need to follow the steps below to provision AWS resources.

  1. Provision AWS resources using the following AWS CloudFormation template in the CLI:

    aws cloudformation create-stack \ --stack-name AWSAppSyncTutorialForAmazonDynamoDB \ --template-url https://s3.us-west-2.amazonaws.com/awsappsync/resources/dynamodb/AmazonDynamoDBCFTemplate.yaml \ --capabilities CAPABILITY_NAMED_IAM

    Alternatively, you can launch the following CloudFormation stack in the US-West 2 (Oregon) region in your AWS account.

    Blue button labeled "Launch Stack" with an arrow icon indicating an action to start.

    This creates the following:

    • A DynamoDB table called AppSyncTutorial-Post that will hold Post data.

    • An IAM role and associated IAM managed policy to allow AWS AppSync to interact with the Post table.

  2. To see more details about the stack and the created resources, run the following CLI command:

    aws cloudformation describe-stacks --stack-name AWSAppSyncTutorialForAmazonDynamoDB
  3. To delete the resources later, you can run the following:

    aws cloudformation delete-stack --stack-name AWSAppSyncTutorialForAmazonDynamoDB

Creating your GraphQL API

To create the GraphQL API in AWS AppSync:

  1. Sign in to the AWS Management Console and open the AppSync console.

    1. In the APIs dashboard, choose Create API.

  2. Under the Customize your API or import from Amazon DynamoDB window, choose Build from scratch.

    1. Choose Start to the right of the same window.

  3. In the API name field, set the name of the API to AWSAppSyncTutorial.

  4. Choose Create.

The AWS AppSync console creates a new GraphQL API for you using the API key authentication mode. You can use the console to set up the rest of the GraphQL API and run queries against it for the rest of this tutorial.

Defining a basic post API

Now that you have created an AWS AppSync GraphQL API, you can set up a basic schema that allows the basic creation, retrieval, and deletion of post data.

  1. Sign in to the AWS Management Console and open the AppSync console.

    1. In the APIs dashboard, choose the API you just created.

  2. In the Sidebar, choose Schema.

    1. In the Schema pane, replace the contents with the following code:

      schema { query: Query mutation: Mutation } type Query { getPost(id: ID): Post } type Mutation { addPost( id: ID! author: String! title: String! content: String! url: String! ): Post! } type Post { id: ID! author: String title: String content: String url: String ups: Int! downs: Int! version: Int! }
  3. Choose Save.

This schema defines a Post type and operations to add and get Post objects.

Configuring the Data Source for the DynamoDB Tables

Next, link the queries and mutations defined in the schema to the AppSyncTutorial-PostDynamoDB table.

First, AWS AppSync needs to be aware of your tables. You do this by setting up a data source in AWS AppSync:

  1. Sign in to the AWS Management Console and open the AppSync console.

    1. In the APIs dashboard, choose your GraphQL API.

    2. In the Sidebar, choose Data Sources.

  2. Choose Create data source.

    1. For Data source name, enter in PostDynamoDBTable.

    2. For Data source type, choose Amazon DynamoDB table.

    3. For Region, choose US-WEST-2.

    4. For Table name, choose the AppSyncTutorial-Post DynamoDB table.

    5. Create a new IAM role (recommended) or choose an existing role that has the lambda:invokeFunction IAM permission. Existing roles need a trust policy, as explained in the Attaching a data source section.

      The following is an example IAM policy that has the required permissions to perform operations on the resource:

      JSON
      { "Version":"2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "lambda:invokeFunction" ], "Resource": [ "arn:aws:lambda:us-east-1:111122223333:function:myFunction", "arn:aws:lambda:us-east-1:111122223333:function:myFunction:*" ] } ] }
  3. Choose Create.

Setting up the addPost resolver (DynamoDB PutItem)

After AWS AppSync is aware of the DynamoDB table, you can link it to individual queries and mutations by defining Resolvers. The first resolver you create is the addPost resolver, which enables you to create a post in the AppSyncTutorial-Post DynamoDB table.

A resolver has the following components:

  • The location in the GraphQL schema to attach the resolver. In this case, you are setting up a resolver on the addPost field on the Mutation type. This resolver will be invoked when the caller calls mutation { addPost(...){...} }.

  • The data source to use for this resolver. In this case, you want to use the PostDynamoDBTable data source you defined earlier, so you can add entries into the AppSyncTutorial-Post DynamoDB table.

  • The request mapping template. The purpose of the request mapping template is to take the incoming request from the caller and translate it into instructions for AWS AppSync to perform against DynamoDB.

  • The response mapping template. The job of the response mapping template is to take the response from DynamoDB and translate it back into something that GraphQL expects. This is useful if the shape of the data in DynamoDB is different to the Post type in GraphQL, but in this case they have the same shape, so you just pass the data through.

To set up the resolver:

  1. Sign in to the AWS Management Console and open the AppSync console.

    1. In the APIs dashboard, choose your GraphQL API.

    2. In the Sidebar, choose Data Sources.

  2. Choose Create data source.

    1. For Data source name, enter in PostDynamoDBTable.

    2. For Data source type, choose Amazon DynamoDB table.

    3. For Region, choose US-WEST-2.

    4. For Table name, choose the AppSyncTutorial-Post DynamoDB table.

    5. Create a new IAM role (recommended) or choose an existing role that has the lambda:invokeFunction IAM permission. Existing roles need a trust policy, as explained in the Attaching a data source section.

      The following is an example IAM policy that has the required permissions to perform operations on the resource:

      JSON
      { "Version":"2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "lambda:invokeFunction" ], "Resource": [ "arn:aws:lambda:us-west-2:123456789012:function:myFunction", "arn:aws:lambda:us-west-2:123456789012:function:myFunction:*" ] } ] }
  3. Choose Create.

  4. Choose the Schema tab.

  5. In the Data types pane on the right, find the addPost field on the Mutation type, and then choose Attach.

  6. In the Action menu, choose Update runtime, then choose Unit Resolver (VTL only).

  7. In Data source name, choose PostDynamoDBTable.

  8. In Configure the request mapping template, paste the following:

    { "version" : "2017-02-28", "operation" : "PutItem", "key" : { "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id) }, "attributeValues" : { "author" : $util.dynamodb.toDynamoDBJson($context.arguments.author), "title" : $util.dynamodb.toDynamoDBJson($context.arguments.title), "content" : $util.dynamodb.toDynamoDBJson($context.arguments.content), "url" : $util.dynamodb.toDynamoDBJson($context.arguments.url), "ups" : { "N" : 1 }, "downs" : { "N" : 0 }, "version" : { "N" : 1 } } }

    Note: A type is specified on all the keys and attribute values. For example, you set the author field to { "S" : "${context.arguments.author}" }. The S part indicates to AWS AppSync and DynamoDB that the value will be a string value. The actual value gets populated from the author argument. Similarly, the version field is a number field because it uses N for the type. Finally, you’re also initializing the ups, downs and version field.

    For this tutorial you’ve specified that the GraphQL ID! type, which indexes the new item that is inserted to DynamoDB, comes as part of the client arguments. AWS AppSync comes with a utility for automatic ID generation called $utils.autoId() which you could have also used in the form of "id" : { "S" : "${$utils.autoId()}" }. Then you could simply leave the id: ID! out of the schema definition of addPost() and it would be inserted automatically. You won’t use this technique for this tutorial, but you should consider it as a good practice when writing to DynamoDB tables.

    For more information about mapping templates, see the Resolver Mapping Template Overview reference documentation. For more information about GetItem request mapping, see the GetItem reference documentation. For more information about types, see the Type System (Request Mapping) reference documentation.

  9. In Configure the response mapping template, paste the following:

    $utils.toJson($context.result)

    Note: Because the shape of the data in the AppSyncTutorial-Post table exactly matches the shape of the Post type in GraphQL, the response mapping template just passes the results straight through. Also note that all of the examples in this tutorial use the same response mapping template, so you only create one file.

  10. Choose Save.

Call the API to Add a Post

Now that the resolver is set up, AWS AppSync can translate an incoming addPost mutation to a DynamoDB PutItem operation. You can now run a mutation to put something in the table.

  • Choose the Queries tab.

  • In the Queries pane, paste the following mutation:

    mutation addPost { addPost( id: 123 author: "AUTHORNAME" title: "Our first post!" content: "This is our first post." url: "https://aws.amazon.com/appsync/" ) { id author title content url ups downs version } }
  • Choose Execute query (the orange play button).

  • The results of the newly created post should appear in the results pane to the right of the query pane. It should look similar to the following:

    { "data": { "addPost": { "id": "123", "author": "AUTHORNAME", "title": "Our first post!", "content": "This is our first post.", "url": "https://aws.amazon.com/appsync/", "ups": 1, "downs": 0, "version": 1 } } }

Here’s what happened:

  • AWS AppSync received an addPost mutation request.

  • AWS AppSync took the request, and the request mapping template, and generated a request mapping document. This would have looked like:

    { "version" : "2017-02-28", "operation" : "PutItem", "key" : { "id" : { "S" : "123" } }, "attributeValues" : { "author": { "S" : "AUTHORNAME" }, "title": { "S" : "Our first post!" }, "content": { "S" : "This is our first post." }, "url": { "S" : "https://aws.amazon.com/appsync/" }, "ups" : { "N" : 1 }, "downs" : { "N" : 0 }, "version" : { "N" : 1 } } }
  • AWS AppSync used the request mapping document to generate and execute a DynamoDBPutItem request.

  • AWS AppSync took the results of the PutItem request and converted them back to GraphQL types.

    { "id" : "123", "author": "AUTHORNAME", "title": "Our first post!", "content": "This is our first post.", "url": "https://aws.amazon.com/appsync/", "ups" : 1, "downs" : 0, "version" : 1 }
  • Passed it through the response mapping document, which just passed it through unchanged.

  • Returned the newly created object in the GraphQL response.

Setting Up the getPost Resolver (DynamoDB GetItem)

Now that you’re able to add data to the AppSyncTutorial-PostDynamoDB table, you need to set up the getPost query so it can retrieve that data from the AppSyncTutorial-Post table. To do this, you set up another resolver.

  • Choose the Schema tab.

  • In the Data types pane on the right, find the getPost field on the Query type, and then choose Attach.

  • In the Action menu, choose Update runtime, then choose Unit Resolver (VTL only).

  • In Data source name, choose PostDynamoDBTable.

  • In Configure the request mapping template, paste the following:

    { "version" : "2017-02-28", "operation" : "GetItem", "key" : { "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id) } }
  • In Configure the response mapping template, paste the following:

    $utils.toJson($context.result)
  • Choose Save.

Call the API to Get a Post

Now the resolver has been set up, AWS AppSync knows how to translate an incoming getPost query to a DynamoDBGetItem operation. You can now run a query to retrieve the post you created earlier.

  • Choose the Queries tab.

  • In the Queries pane, paste the following:

    query getPost { getPost(id:123) { id author title content url ups downs version } }
  • Choose Execute query (the orange play button).

  • The post retrieved from DynamoDB should appear in the results pane to the right of the query pane. It should look similar to the following:

    { "data": { "getPost": { "id": "123", "author": "AUTHORNAME", "title": "Our first post!", "content": "This is our first post.", "url": "https://aws.amazon.com/appsync/", "ups": 1, "downs": 0, "version": 1 } } }

Here’s what happened:

  • AWS AppSync received a getPost query request.

  • AWS AppSync took the request, and the request mapping template, and generated a request mapping document. This would have looked like:

    { "version" : "2017-02-28", "operation" : "GetItem", "key" : { "id" : { "S" : "123" } } }
  • AWS AppSync used the request mapping document to generate and execute a DynamoDB GetItem request.

  • AWS AppSync took the results of the GetItem request and converted it back to GraphQL types.

    { "id" : "123", "author": "AUTHORNAME", "title": "Our first post!", "content": "This is our first post.", "url": "https://aws.amazon.com/appsync/", "ups" : 1, "downs" : 0, "version" : 1 }
  • Passed it through the response mapping document, which just passed it through unchanged.

  • Returned the retrieved object in the response.

Alternatively, take the following example:

query getPost { getPost(id:123) { id author title } }

If your getPost query only needs the id, author, and title, you can change your request mapping template to use projection expressions to specify only the attributes that you want from your DynamoDB table to avoid unnecessary data transfer from DynamoDB to AWS AppSync. For example, the request mapping template may look like the snippet below:

{ "version" : "2017-02-28", "operation" : "GetItem", "key" : { "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id) }, "projection" : { "expression" : "#author, id, title", "expressionNames" : { "#author" : "author"} } }

Create an updatePost Mutation (DynamoDB UpdateItem)

So far you can create and retrieve Post objects in DynamoDB. Next, you’ll set up a new mutation to allow us to update object. You’ll do this using the UpdateItem DynamoDB operation.

  • Choose the Schema tab.

  • In the Schema pane, modify the Mutation type to add a new updatePost mutation as follows:

    type Mutation { updatePost( id: ID!, author: String!, title: String!, content: String!, url: String! ): Post addPost( author: String! title: String! content: String! url: String! ): Post! }
  • Choose Save.

  • In the Data types pane on the right, find the newly created updatePost field on the Mutation type and then choose Attach.

  • In the Action menu, choose Update runtime, then choose Unit Resolver (VTL only).

  • In Data source name, choose PostDynamoDBTable.

  • In Configure the request mapping template, paste the following:

    { "version" : "2017-02-28", "operation" : "UpdateItem", "key" : { "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id) }, "update" : { "expression" : "SET author = :author, title = :title, content = :content, #url = :url ADD version :one", "expressionNames": { "#url" : "url" }, "expressionValues": { ":author" : $util.dynamodb.toDynamoDBJson($context.arguments.author), ":title" : $util.dynamodb.toDynamoDBJson($context.arguments.title), ":content" : $util.dynamodb.toDynamoDBJson($context.arguments.content), ":url" : $util.dynamodb.toDynamoDBJson($context.arguments.url), ":one" : { "N": 1 } } } }

    Note: This resolver is using the DynamoDB UpdateItem, which is significantly different from the PutItem operation. Instead of writing the entire item, you’re just asking DynamoDB to update certain attributes. This is done using DynamoDB Update Expressions. The expression itself is specified in the expression field in the update section. It says to set the author, title, content and url attributes, and then increment the version field. The values to use do not appear in the expression itself; the expression has placeholders that have names starting with a colon, which are then defined in the expressionValues field. Finally, DynamoDB has reserved words that cannot appear in the expression. For example, url is a reserved word, so to update the url field you can use name placeholders and define them in the expressionNames field.

    For more info about UpdateItem request mapping, see the UpdateItem reference documentation. For more information about how to write update expressions, see the DynamoDB UpdateExpressions documentation.

  • In Configure the response mapping template, paste the following:

    $utils.toJson($context.result)

Call the API to Update a Post

Now the resolver has been set up, AWS AppSync knows how to translate an incoming update mutation to a DynamoDBUpdate operation. You can now run a mutation to update the item you wrote earlier.

  • Choose the Queries tab.

  • In Queries pane, paste the following mutation. You’ll also need to update the id argument to the value you noted down earlier.

    mutation updatePost { updatePost( id:"123" author: "A new author" title: "An updated author!" content: "Now with updated content!" url: "https://aws.amazon.com/appsync/" ) { id author title content url ups downs version } }
  • Choose Execute query (the orange play button).

  • The updated post in DynamoDB should appear in the results pane to the right of the query pane. It should look similar to the following:

    { "data": { "updatePost": { "id": "123", "author": "A new author", "title": "An updated author!", "content": "Now with updated content!", "url": "https://aws.amazon.com/appsync/", "ups": 1, "downs": 0, "version": 2 } } }

In this example, the ups and downs fields were not modified because the request mapping template did not ask AWS AppSync and DynamoDB to do anything with those fields. Also, the version field was incremented by 1 because you asked AWS AppSync and DynamoDB to add 1 to the version field.

Modifying the updatePost Resolver (DynamoDB UpdateItem)

This is a good start to the updatePost mutation, but it has two main problems:

  • If you want to update just a single field, you have to update all of the fields.

  • If two people are modifying the object, you could potentially lose information.

To address these issues, you’re going to modify the updatePost mutation to only modify arguments that were specified in the request, and then add a condition to the UpdateItem operation.

  1. Choose the Schema tab.

  2. In the Schema pane, modify the updatePost field in the Mutation type to remove the exclamation marks from the