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.
-
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 AWS CloudFormation stack in the US-West 2 (Oregon) region in your AWS account.
This creates the following:
-
A DynamoDB table called
AppSyncTutorial-Post
that will holdPost
data. -
An IAM role and associated IAM managed policy to allow AWS AppSync to interact with the
Post
table.
-
-
To see more details about the stack and the created resources, run the following CLI command:
aws cloudformation describe-stacks --stack-name AWSAppSyncTutorialForAmazonDynamoDB
-
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:
-
Sign in to the AWS Management Console and open the AppSync console
. -
In the APIs dashboard, choose Create API.
-
-
Under the Customize your API or import from Amazon DynamoDB window, choose Build from scratch.
-
Choose Start to the right of the same window.
-
-
In the API name field, set the name of the API to
AWSAppSyncTutorial
. -
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.
-
Sign in to the AWS Management Console and open the AppSync console
. -
In the APIs dashboard, choose the API you just created.
-
-
In the Sidebar, choose Schema.
-
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! }
-
-
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-Post
DynamoDB
table.
First, AWS AppSync needs to be aware of your tables. You do this by setting up a data source in AWS AppSync:
-
Sign in to the AWS Management Console and open the AppSync console
. -
In the APIs dashboard, choose your GraphQL API.
-
In the Sidebar, choose Data Sources.
-
-
Choose Create data source.
-
For Data source name, enter in
PostDynamoDBTable
. -
For Data source type, choose Amazon DynamoDB table.
-
For Region, choose US-WEST-2.
-
For Table name, choose the AppSyncTutorial-Post DynamoDB table.
-
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:
{ "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:*" ] } ] }
-
-
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 theMutation
type. This resolver will be invoked when the caller callsmutation { 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 theAppSyncTutorial-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:
-
Sign in to the AWS Management Console and open the AppSync console
. -
In the APIs dashboard, choose your GraphQL API.
-
In the Sidebar, choose Data Sources.
-
-
Choose Create data source.
-
For Data source name, enter in
PostDynamoDBTable
. -
For Data source type, choose Amazon DynamoDB table.
-
For Region, choose US-WEST-2.
-
For Table name, choose the AppSyncTutorial-Post DynamoDB table.
-
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:
{ "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:*" ] } ] }
-
-
Choose Create.
-
Choose the Schema tab.
-
In the Data types pane on the right, find the addPost 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" : "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}" }
. TheS
part indicates to AWS AppSync and DynamoDB that the value will be a string value. The actual value gets populated from theauthor
argument. Similarly, theversion
field is a number field because it usesN
for the type. Finally, you’re also initializing theups
,downs
andversion
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 theid: ID!
out of the schema definition ofaddPost()
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.
-
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 thePost
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. -
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 DynamoDB
PutItem
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-Post
DynamoDB 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 newupdatePost
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 theupdate
section. It says to set theauthor
,title
,content
and url attributes, and then increment theversion
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 theexpressionValues
field. Finally, DynamoDB has reserved words that cannot appear in theexpression
. For example,url
is a reserved word, so to update theurl
field you can use name placeholders and define them in theexpressionNames
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.
-
Choose the Schema tab.
-
In the Schema pane, modify the
updatePost
field in theMutation
type to remove the exclamation marks from theauthor
,title
,content
, andurl
arguments, making sure to leave theid
field as is. This will make them optional argument. Also, add a new, requiredexpectedVersion
argument.type Mutation { updatePost( id: ID!, author: String, title: String, content: String, url: String, expectedVersion: Int! ): Post addPost( author: String! title: String! content: String! url: String! ): Post! }
-
Choose Save.
-
In the Data types pane on the right, find the updatePost field on the Mutation type.
-
Choose PostDynamoDBTable to open the existing resolver.
-
In Configure the request mapping template, modify the request mapping template as follows:
{ "version" : "2017-02-28", "operation" : "UpdateItem", "key" : { "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id) }, ## Set up some space to keep track of things you're updating ** #set( $expNames = {} ) #set( $expValues = {} ) #set( $expSet = {} ) #set( $expAdd = {} ) #set( $expRemove = [] ) ## Increment "version" by 1 ** $!{expAdd.put("version", ":one")} $!{expValues.put(":one", { "N" : 1 })} ## Iterate through each argument, skipping "id" and "expectedVersion" ** #foreach( $entry in $context.arguments.entrySet() ) #if( $entry.key != "id" && $entry.key != "expectedVersion" ) #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 you'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 you'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 you'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 }, "condition" : { "expression" : "version = :expectedVersion", "expressionValues" : { ":expectedVersion" : $util.dynamodb.toDynamoDBJson($context.arguments.expectedVersion) } } }
-
Choose Save.
This template is one of the more complex examples. It demonstrates the power and
flexibility of mapping templates. It loops through all of the arguments, skipping over
id
and expectedVersion
. If the argument is set to something, it
asks AWS AppSync and DynamoDB to update that attribute on the object in DynamoDB. If the attribute is
set to null, it asks AWS AppSync and DynamoDB to remove that attribute from the post object. If an
argument wasn’t specified, it leaves the attribute alone. It also increments the
version
field.
Also, there is a new condition
section. A condition expression enables you
tell AWS AppSync and DynamoDB whether or not the request should succeed based on the state of the
object already in DynamoDB before the operation is performed. In this case, you only want the
UpdateItem
request to succeed if the version
field of the item
currently in DynamoDB exactly matches the expectedVersion
argument.
For more information about condition expressions, see the Condition Expressions reference documentation.
Call the API to Update a Post
Let’s try updating the Post
object with the new resolver:
-
Choose the Queries tab.
-
In the 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 title: "An empty story" content: null expectedVersion: 2 ) { 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 empty story", "content": null, "url": "https://aws.amazon.com/appsync/", "ups": 1, "downs": 0, "version": 3 } } }
In this request, you asked AWS AppSync and DynamoDB to update the title
and
content
field only. It left all the other fields alone (other than
incrementing the version
field). You set the title
attribute to a
new value, and removed the content
attribute from the post. The
author
, url
, ups
, and downs
fields
were left untouched.
Try executing the mutation request again, leaving the request exactly as is. You should see a response similar to the following:
{ "data": { "updatePost": null }, "errors": [ { "path": [ "updatePost" ], "data": { "id": "123", "author": "A new author", "title": "An empty story", "content": null, "url": "https://aws.amazon.com/appsync/", "ups": 1, "downs": 0, "version": 3 }, "errorType": "DynamoDB:ConditionalCheckFailedException", "locations": [ { "line": 2, "column": 3 } ], "message": "The conditional request failed (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ConditionalCheckFailedException; Request ID: ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ)" } ] }
The request fails because the condition expression evaluates to false:
-
The first time you ran the request, the value of the
version
field of the post in DynamoDB was2
, which matched theexpectedVersion
argument. The request succeeded, which meant theversion
field was incremented in DynamoDB to3
. -
The second time you ran the request, the value of the
version
field of the post in DynamoDB was3
, which did not match theexpectedVersion
argument.
This pattern is typically called optimistic locking.
A feature of an AWS AppSync DynamoDB resolver is that it returns the current value of the post
object in DynamoDB. You can find this in the data
field in the errors
section of the GraphQL response. Your application can use this information to decide how it
should proceed. In this case, you can see the version
field of the object in
DynamoDB is set to 3
, so you could just update the expectedVersion
argument to 3
and the request would succeed again.
For more information about handling condition check failures, see the Condition Expressions mapping template reference documentation.
Create upvotePost and downvotePost Mutations (DynamoDB UpdateItem)
The Post
type has ups
and downs
fields to enable
record upvotes and downvotes, but so far the API doesn’t let us do anything with them. Let’s
add some mutations to let us upvote and downvote the posts.
-
Choose the Schema tab.
-
In the Schema pane, modify the
Mutation
type to add newupvotePost
anddownvotePost
mutations as follows:type Mutation { upvotePost(id: ID!): Post downvotePost(id: ID!): Post updatePost( id: ID!, author: String, title: String, content: String, url: String, expectedVersion: Int! ): 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 upvotePost 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" : "ADD ups :plusOne, version :plusOne", "expressionValues" : { ":plusOne" : { "N" : 1 } } } }
-
In Configure the response mapping template, paste the following:
$utils.toJson($context.result)
-
Choose Save.
-
In the Data types pane on the right, find the newly created
downvotePost
field on the Mutation type, and then choose Attach. -
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" : "ADD downs :plusOne, version :plusOne", "expressionValues" : { ":plusOne" : { "N" : 1 } } } }
-
In Configure the response mapping template, paste the following:
$utils.toJson($context.result)
-
Choose Save.
Call the API to upvote and downvote a Post
Now the new resolvers have been set up, AWS AppSync knows how to translate an incoming
upvotePost
or downvote
mutation to DynamoDB UpdateItem operation.
You can now run mutations to upvote or downvote the post you created earlier.
-
Choose the Queries tab.
-
In the Queries pane, paste the following mutation. You’ll also need to update the
id
argument to the value you noted down earlier.mutation votePost { upvotePost(id:123) { id author title content url ups downs version } }
-
Choose Execute query (the orange play button).
-
The post is updated in DynamoDB and should appear in the results pane to the right of the query pane. It should look similar to the following:
{ "data": { "upvotePost": { "id": "123", "author": "A new author", "title": "An empty story", "content": null, "url": "https://aws.amazon.com/appsync/", "ups": 6, "downs": 0, "version": 4 } } }
-
Choose Execute query a few more times. You should see the
ups
andversion
field incrementing by 1 each time you execute the query. -
Change the query to call the
downvotePost
mutation as follows:mutation votePost { downvotePost(id:123) { id author title content url ups downs version } }
-
Choose Execute query (the orange play button). This time, you should see the
downs
andversion
field incrementing by 1 each time you execute the query.{ "data": { "downvotePost": { "id": "123", "author": "A new author", "title": "An empty story", "content": null, "url": "https://aws.amazon.com/appsync/", "ups": 6, "downs": 4, "version": 12 } } }
Setting Up the deletePost Resolver (DynamoDB DeleteItem)
The next mutation you want to set up is to delete a post. You’ll do this using the
DeleteItem
DynamoDB operation.
-
Choose the Schema tab.
-
In the Schema pane, modify the
Mutation
type to add a newdeletePost
mutation as follows:type Mutation { deletePost(id: ID!, expectedVersion: Int): Post upvotePost(id: ID!): Post downvotePost(id: ID!): Post updatePost( id: ID!, author: String, title: String, content: String, url: String, expectedVersion: Int! ): Post addPost( author: String!, title: String!, content: String!, url: String! ): Post! }
This time you made the
expectedVersion
field optional, which is explained later when you add the request mapping template. -
Choose Save.
-
In the Data types pane on the right, find the newly created delete 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" : "DeleteItem", "key": { "id": $util.dynamodb.toDynamoDBJson($context.arguments.id) } #if( $context.arguments.containsKey("expectedVersion") ) ,"condition" : { "expression" : "attribute_not_exists(id) OR version = :expectedVersion", "expressionValues" : { ":expectedVersion" : $util.dynamodb.toDynamoDBJson($context.arguments.expectedVersion) } } #end }
Note: The
expectedVersion
argument is an optional argument. If the caller set anexpectedVersion
argument in the request, the template adds a condition that only allows theDeleteItem
request to succeed if the item is already deleted or if theversion
attribute of the post in DynamoDB exactly matches theexpectedVersion
. If left out, no condition expression is specified on theDeleteItem
request. It succeeds regardless of the value ofversion
, or whether or not the item exists in DynamoDB. -
In Configure the response mapping template, paste the following:
$utils.toJson($context.result)
Note: Even though you’re deleting an item, you can return the item that was deleted, if it was not already deleted.
-
Choose Save.
For more info about DeleteItem
request mapping, see the DeleteItem reference documentation.
Call the API to Delete a Post
Now the resolver has been set up, AWS AppSync knows how to translate an incoming
delete
mutation to a DynamoDBDeleteItem
operation. You can now run
a mutation to delete something in the table.
-
Choose the Queries tab.
-
In the Queries pane, paste the following mutation. You’ll also need to update the
id
argument to the value you noted down earlier.mutation deletePost { deletePost(id:123) { id author title content url ups downs version } }
-
Choose Execute query (the orange play button).
-
The post is deleted from DynamoDB. Note that AWS AppSync returns the value of the item that was deleted from DynamoDB, which should appear in the results pane to the right of the query pane. It should look similar to the following:
{ "data": { "deletePost": { "id": "123", "author": "A new author", "title": "An empty story", "content": null, "url": "https://aws.amazon.com/appsync/", "ups": 6, "downs": 4, "version": 12 } } }
The value is only returned if this call to deletePost
was the one that
actually deleted it from DynamoDB.
-
Choose Execute query again.
-
The call still succeeds, but no value is returned.
{ "data": { "deletePost": null } }
Now let’s try deleting a post, but this time specifying an expectedValue
.
First though, you’ll need to create a new post because you’ve just deleted the one you’ve
been working with so far.
-
In the Queries pane, paste the following mutation:
mutation addPost { addPost( id:123 author: "AUTHORNAME" title: "Our second post!" content: "A new 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. Note down the
id
of the newly created object because you need it in just a moment. It should look similar to the following:{ "data": { "addPost": { "id": "123", "author": "AUTHORNAME", "title": "Our second post!", "content": "A new post.", "url": "https://aws.amazon.com/appsync/", "ups": 1, "downs": 0, "version": 1 } } }
Now let’s try to delete that post, but put in the wrong value for
expectedVersion
:
-
In the Queries pane, paste the following mutation. You’ll also need to update the
id
argument to the value you noted down earlier.mutation deletePost { deletePost( id:123 expectedVersion: 9999 ) { id author title content url ups downs version } }
-
Choose Execute query (the orange play button).
{ "data": { "deletePost": null }, "errors": [ { "path": [ "deletePost" ], "data": { "id": "123", "author": "AUTHORNAME", "title": "Our second post!", "content": "A new post.", "url": "https://aws.amazon.com/appsync/", "ups": 1, "downs": 0, "version": 1 }, "errorType": "DynamoDB:ConditionalCheckFailedException", "locations": [ { "line": 2, "column": 3 } ], "message": "The conditional request failed (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ConditionalCheckFailedException; Request ID: ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ)" } ] }
The request failed because the condition expression evaluates to false: the value for
version
of the post in DynamoDB does not match theexpectedValue
specified in the arguments. The current value of the object is returned in thedata
field in theerrors
section of the GraphQL response. -
Retry the request, but correct the
expectedVersion
:mutation deletePost { deletePost( id:123 expectedVersion: 1 ) { id author title content url ups downs version } }
-
Choose Execute query (the orange play button).
-
This time the request succeeds, and the value that was deleted from DynamoDB is returned:
{ "data": { "deletePost": { "id": "123", "author": "AUTHORNAME", "title": "Our second post!", "content": "A new post.", "url": "https://aws.amazon.com/appsync/", "ups": 1, "downs": 0, "version": 1 } } }
-
Choose Execute query again.
-
The call still succeeds, but this time no value is returned because the post was already deleted in DynamoDB.
{ "data": { "deletePost": null } }
Setting Up the allPost Resolver (DynamoDB Scan)
So far the API is only useful if you know the id
of each post you want to
look at. Let’s add a new resolver that returns all the posts in the table.
-
Choose the Schema tab.
-
In the Schema pane, modify the
Query
type to add a newallPost
query as follows:type Query { allPost(count: Int, nextToken: String): PaginatedPosts! getPost(id: ID): Post }
-
Add a new
PaginationPosts
type:type PaginatedPosts { posts: [Post!]! nextToken: String }
-
Choose Save.
-
In the Data types pane on the right, find the newly created allPost 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" : "Scan" #if( ${context.arguments.count} ) ,"limit": $util.toJson($context.arguments.count) #end #if( ${context.arguments.nextToken} ) ,"nextToken": $util.toJson($context.arguments.nextToken) #end }
This resolver has two optional arguments:
count
, which specifies the maximum number of items to return in a single call, andnextToken
, which can be used to retrieve the next set of results (you’ll show where the value fornextToken
comes from later). -
In Configure the response mapping template, paste the following:
{ "posts": $utils.toJson($context.result.items) #if( ${context.result.nextToken} ) ,"nextToken": $util.toJson($context.result.nextToken) #end }
Note: This response mapping template is different from all the others so far. The result of the
allPost
query is aPaginatedPosts
, which contains a list of posts and a pagination token. The shape of this object is different to what is returned from the AWS AppSync DynamoDB Resolver: the list of posts is calleditems
in the AWS AppSync DynamoDB Resolver results, but is calledposts
inPaginatedPosts
. -
Choose Save.
For more information about Scan
request mapping, see the Scan
reference documentation.
Call the API to Scan All Posts
Now the resolver has been set up, AWS AppSync knows how to translate an incoming
allPost
query to a DynamoDBScan
operation. You can now scan the
table to retrieve all the posts.
Before you can try it out though, you need to populate the table with some data because you’ve deleted everything you’ve worked with so far.
-
Choose the Queries tab.
-
In the Queries pane, paste the following mutation:
mutation addPost { post1: addPost(id:1 author: "AUTHORNAME" title: "A series of posts, Volume 1" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title } post2: addPost(id:2 author: "AUTHORNAME" title: "A series of posts, Volume 2" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title } post3: addPost(id:3 author: "AUTHORNAME" title: "A series of posts, Volume 3" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title } post4: addPost(id:4 author: "AUTHORNAME" title: "A series of posts, Volume 4" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title } post5: addPost(id:5 author: "AUTHORNAME" title: "A series of posts, Volume 5" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title } post6: addPost(id:6 author: "AUTHORNAME" title: "A series of posts, Volume 6" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title } post7: addPost(id:7 author: "AUTHORNAME" title: "A series of posts, Volume 7" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title } post8: addPost(id:8 author: "AUTHORNAME" title: "A series of posts, Volume 8" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title } post9: addPost(id:9 author: "AUTHORNAME" title: "A series of posts, Volume 9" content: "Some content" url: "https://aws.amazon.com/appsync/" ) { title } }
-
Choose Execute query (the orange play button).
Now, let’s scan the table, returning five results at a time.
-
In the Queries pane, paste the following query:
query allPost { allPost(count: 5) { posts { id title } nextToken } }
-
Choose Execute query (the orange play button).
-
The first five posts should appear in the results pane to the right of the query pane. It should look similar to the following:
{ "data": { "allPost": { "posts": [ { "id": "5", "title": "A series of posts, Volume 5" }, { "id": "1", "title": "A series of posts, Volume 1" }, { "id": "6", "title": "A series of posts, Volume 6" }, { "id": "9", "title": "A series of posts, Volume 9" }, { "id": "7", "title": "A series of posts, Volume 7" } ], "nextToken": "eyJ2ZXJzaW9uIjoxLCJ0b2tlbiI6IkFRSUNBSGo4eHR0RG0xWXhUa1F0cEhXMEp1R3B0M1B3eThOSmRvcG9ad2RHYjI3Z0lnRkJEdXdUK09hcnovRGhNTGxLTGdMUEFBQUI1akNDQWVJR0NTcUdTSWIzRFFFSEJxQ0NBZE13Z2dIUEFnRUFNSUlCeUFZSktvWklodmNOQVFjQk1CNEdDV0NHU0FGbEF3UUJMakFSQkF6ajFodkhKU1paT1pncTRaUUNBUkNBZ2dHWnJiR1dQWGxkMDB1N0xEdGY4Z2JsbktzRjRua1VCcks3TFJLcjZBTFRMeGFwVGJZMDRqOTdKVFQyYVRwSzdzbVdtNlhWWFVCTnFIOThZTzBWZHVkdDI2RlkxMHRqMDJ2QTlyNWJTUWpTbWh6NE5UclhUMG9KZWJSQ2JJbXBlaDRSVlg0Tis0WTVCN1IwNmJQWWQzOVhsbTlUTjBkZkFYMVErVCthaXZoNE5jMk50RitxVmU3SlJ5WmpzMEFkSGduM3FWd2VrOW5oeFVVd3JlK1loUks5QkRzemdiMDlmZmFPVXpzaFZ4cVJRbC93RURlOTcrRmVJdXZNby9NZ1F6dUdNbFRyalpNR3FuYzZBRnhwa0VlZTFtR0FwVDFISElUZlluakptYklmMGUzUmcxbVlnVHVSbDh4S0trNmR0QVoraEhLVDhuNUI3VnF4bHRtSnlNUXBrZGl6KzkyL3VzNDl4OWhrMnVxSW01ZFFwMjRLNnF0dm9ZK1BpdERuQTc5djhzb0grVytYT3VuQ2NVVDY4TVZ1Wk5KYkRuSEFSSEVlaTlVNVBTelU5RGZ6d2pPdmhqWDNJMWhwdWUrWi83MDVHVjlPQUxSTGlwZWZPeTFOZFhwZTdHRDZnQW00bUJUK2c1eC9Ec3ZDbWVnSDFDVXRTdHVuU1ZFa2JpZytQRC9oMUwyRTNqSHhVQldaa28yU256WUc0cG0vV1RSWkFVZHZuQT09In0=" } } }
You got five results and a nextToken
that you can use to get the next set
of results.
-
Update the
allPost
query to include thenextToken
from the previous set of results:query allPost { allPost( count: 5 nextToken: "eyJ2ZXJzaW9uIjoxLCJ0b2tlbiI6IkFRSUNBSGo4eHR0RG0xWXhUa1F0cEhXMEp1R3B0M1B3eThOSmRvcG9ad2RHYjI3Z0lnRlluNktJRWl6V0ZlR3hJOVJkaStrZUFBQUI1akNDQWVJR0NTcUdTSWIzRFFFSEJxQ0NBZE13Z2dIUEFnRUFNSUlCeUFZSktvWklodmNOQVFjQk1CNEdDV0NHU0FGbEF3UUJMakFSQkF5cW8yUGFSZThnalFpemRCTUNBUkNBZ2dHWk1JODhUNzhIOFVUZGtpdFM2ZFluSWRyVDg4c2lkN1RjZzB2d1k3VGJTTWpSQ2U3WjY3TkUvU2I1dWNETUdDMmdmMHErSGJSL0pteGRzYzVEYnE1K3BmWEtBdU5jSENJdWNIUkJ0UHBPWVdWdCtsS2U5L1pNcWdocXhrem1RaXI1YnIvQkt6dU5hZmJCdE93NmtoM2Jna1BKM0RjWWhpMFBGbmhMVGg4TUVGSjBCcXg3RTlHR1V5N0tUS0JLZlV3RjFQZ0JRREdrNzFYQnFMK2R1S2IrVGtZZzVYMjFrc3NyQmFVTmNXZmhTeXE0ZUJHSWhqZWQ5c3VKWjBSSTc2ZnVQdlZkR3FLNENjQmxHYXhpekZnK2pKK1FneEU1SXduRTNYYU5TR0I4QUpmamR2bU1wbUk1SEdvWjlMUUswclczbG14RDRtMlBsaTNLaEVlcm9pem5zcmdINFpvcXIrN2ltRDN3QkJNd3BLbGQzNjV5Nnc4ZnMrK2FnbTFVOUlKOFFrOGd2bEgySHFROHZrZXBrMWlLdWRIQ25LaS9USnBlMk9JeEVPazVnRFlzRTRUU09HUlVJTkxYY2MvdW1WVEpBMUthV2hWTlAvdjNlSnlZQUszbWV6N2h5WHVXZ1BkTVBNWERQdTdjVnVRa3EwK3NhbGZOd2wvSUx4bHNyNDVwTEhuVFpyRWZvVlV1bXZ5S2VKY1RUU1lET05hM1NwWEd2UT09In0=" ) { posts { id author } nextToken } }
-
Choose Execute query (the orange play button).
-
The remaining four posts should appear in the results pane to the right of the query pane. There is no
nextToken
in this set of results because you’ve paged through all nine posts, with none remaining. It should look similar to the following:{ "data": { "allPost": { "posts": [ { "id": "2", "title": "A series of posts, Volume 2" }, { "id": "3", "title": "A series of posts, Volume 3" }, { "id": "4", "title": "A series of posts, Volume 4" }, { "id": "8", "title": "A series of posts, Volume 8" } ], "nextToken": null } } }
Setting Up the allPostsByAuthor Resolver (DynamoDB Query)
In addition to scanning DynamoDB for all posts, you can also query DynamoDB to retrieve posts
created by a specific author. The DynamoDB table you created earlier already has a
GlobalSecondaryIndex
called author-index
you can use with a
DynamoDBQuery
operation to retrieve all posts created by a specific author.
-
Choose the Schema tab.
-
In the Schema pane, modify the
Query
type to add a newallPostsByAuthor
query as follows:type Query { allPostsByAuthor(author: String!, count: Int, nextToken: String): PaginatedPosts! allPost(count: Int, nextToken: String): PaginatedPosts! getPost(id: ID): Post }
Note: This uses the same
PaginatedPosts
type that you used with theallPost
query. -
Choose Save.
-
In the Data types pane on the right, find the newly created allPostsByAuthor 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" : "Query", "index" : "author-index", "query" : { "expression": "author = :author", "expressionValues" : { ":author" : $util.dynamodb.toDynamoDBJson($context.arguments.author) } } #if( ${context.arguments.count} ) ,"limit": $util.toJson($context.arguments.count) #end #if( ${context.arguments.nextToken} ) ,"nextToken": "${context.arguments.nextToken}" #end }
Like the
allPost
resolver, this resolver has two optional arguments:count
, which specifies the maximum number of items to return in a single call, andnextToken
, which can be used to retrieve the next set of results (the value fornextToken
can be obtained from a previous call). -
In Configure the response mapping template, paste the following:
{ "posts": $utils.toJson($context.result.items) #if( ${context.result.nextToken} ) ,"nextToken": $util.toJson($context.result.nextToken) #end }
Note: This is the same response mapping template that you used in the
allPost
resolver. -
Choose Save.
For more information about Query
request mapping, see the Query
reference documentation.
Call the API to Query All Posts by an Author
Now the resolver has been set up, AWS AppSync knows how to translate an incoming
allPostsByAuthor
mutation to a DynamoDBQuery
operation against the
author-index
index. You can now query the table to retrieve all the posts by
a specific author.
Before you do that, however, let’s populate the table with some more posts, because every post so far has the same author.
-
Choose the Queries tab.
-
In the Queries pane, paste the following mutation:
mutation addPost { post1: addPost(id:10 author: "Nadia" title: "The cutest dog in the world" content: "So cute. So very, very cute." url: "https://aws.amazon.com/appsync/" ) { author, title } post2: addPost(id:11 author: "Nadia" title: "Did you know...?" content: "AppSync works offline?" url: "https://aws.amazon.com/appsync/" ) { author, title } post3: addPost(id:12 author: "Steve" title: "I like GraphQL" content: "It's great" url: "https://aws.amazon.com/appsync/" ) { author, title } }
-
Choose Execute query (the orange play button).
Now, let’s query the table, returning all posts authored by Nadia
.
-
In the Queries pane, paste the following query:
query allPostsByAuthor { allPostsByAuthor(author: "Nadia") { posts { id title } nextToken } }
-
Choose Execute query (the orange play button).
-
All the posts authored by
Nadia
should appear in the results pane to the right of the query pane. It should look similar to the following:{ "data": { "allPostsByAuthor": { "posts": [ { "id": "10", "title": "The cutest dog in the world" }, { "id": "11", "title": "Did you know...?" } ], "nextToken": null } } }
Pagination works for Query
just the same as it does for Scan
.
For example, let’s look for all posts by AUTHORNAME
, getting five at a
time.
-
In the Queries pane, paste the following query:
query allPostsByAuthor { allPostsByAuthor( author: "AUTHORNAME" count: 5 ) { posts { id title } nextToken } }
-
Choose Execute query (the orange play button).
-
All the posts authored by
AUTHORNAME
should appear in the results pane to the right of the query pane. It should look similar to the following:{ "data": { "allPostsByAuthor": { "posts": [ { "id": "6", "title": "A series of posts, Volume 6" }, { "id": "4", "title": "A series of posts, Volume 4" }, { "id": "2", "title": "A series of posts, Volume 2" }, { "id": "7", "title": "A series of posts, Volume 7" }, { "id": "1", "title": "A series of posts, Volume 1" } ], "nextToken": "eyJ2ZXJzaW9uIjoxLCJ0b2tlbiI6IkFRSUNBSGo4eHR0RG0xWXhUa1F0cEhXMEp1R3B0M1B3eThOSmRvcG9ad2RHYjI3Z0lnSExqRnVhVUR3ZUhEZ2QzNGJ2QlFuY0FBQUNqekNDQW9zR0NTcUdTSWIzRFFFSEJxQ0NBbnd3Z2dKNEFnRUFNSUlDY1FZSktvWklodmNOQVFjQk1CNEdDV0NHU0FGbEF3UUJMakFSQkF5Qkg4Yk1obW9LVEFTZHM3SUNBUkNBZ2dKQ3dISzZKNlJuN3pyYUVKY1pWNWxhSkNtZW1KZ0F5N1dhZkc2UEdTNHpNQzJycTkwZHFJTFV6Z25wck9Gd3pMS3VOQ2JvUXc3VDI5eCtnVExIbGg4S3BqbzB1YjZHQ3FwcDhvNDVmMG9JbDlmdS9JdjNXcFNNSXFKTXZ1MEVGVWs1VzJQaW5jZGlUaVRtZFdYWlU1bkV2NkgyRFBRQWZYYlNnSmlHSHFLbmJZTUZZM0FTdmRIL0hQaVZBb1RCMk1YZkg0eGJOVTdEbjZtRFNhb2QwbzdHZHJEWDNtODQ1UXBQUVNyUFhHemY0WDkyajhIdlBCSWE4Smcrb0RxbHozUVQ5N2FXUXdYWWU2S0h4emI1ejRITXdEdXEyRDRkYzhoMi9CbW10MzRMelVGUVIyaExSZGRaZ0xkdzF5cHJZdFZwY3dEc1d4UURBTzdOcjV2ZEp4VVR2TVhmODBRSnp1REhXREpTVlJLdDJwWmlpaXhXeGRwRmNod1BzQ3d2aVBqMGwrcWFFWU1jMXNQbENkVkFGem43VXJrSThWbS8wWHlwR2xZb3BSL2FkV0xVekgrbGMrYno1ZEM2SnVLVXdtY1EyRXlZeDZiS0Izbi9YdUViWGdFeU5PMWZTdE1rRlhyWmpvMVpzdlYyUFRjMzMrdEs0ZDhkNkZrdjh5VVR6WHhJRkxIaVNsOUx6VVdtT3BCaWhrTFBCT09jcXkyOHh1UmkzOEM3UFRqMmN6c3RkOUo1VUY0azBJdUdEbVZzM2xjdWg1SEJjYThIeXM2aEpvOG1HbFpMNWN6R2s5bi8vRE1EbDY3RlJraG5QNFNhSDBpZGI5VFEvMERLeFRBTUdhcWpPaEl5ekVqd2ZDQVJleFdlbldyOGlPVkhScDhGM25WZVdvbFRGK002N0xpdi9XNGJXdDk0VEg3b0laUU5lYmZYKzVOKy9Td25Hb1dyMTlWK0pEb2lIRVFLZ1cwMWVuYjZKUXo5Slh2Tm95ZzF3RnJPVmxGc2xwNlRHa1BlN2Rnd2IrWT0ifQ==" } } }
-
Update the
nextToken
argument with the value returned from the previous query as follows:query allPostsByAuthor { allPostsByAuthor( author: "AUTHORNAME" count: 5 nextToken: "eyJ2ZXJzaW9uIjoxLCJ0b2tlbiI6IkFRSUNBSGo4eHR0RG0xWXhUa1F0cEhXMEp1R3B0M1B3eThOSmRvcG9ad2RHYjI3Z0lnSExqRnVhVUR3ZUhEZ2QzNGJ2QlFuY0FBQUNqekNDQW9zR0NTcUdTSWIzRFFFSEJxQ0NBbnd3Z2dKNEFnRUFNSUlDY1FZSktvWklodmNOQVFjQk1CNEdDV0NHU0FGbEF3UUJMakFSQkF5Qkg4Yk1obW9LVEFTZHM3SUNBUkNBZ2dKQ3dISzZKNlJuN3pyYUVKY1pWNWxhSkNtZW1KZ0F5N1dhZkc2UEdTNHpNQzJycTkwZHFJTFV6Z25wck9Gd3pMS3VOQ2JvUXc3VDI5eCtnVExIbGg4S3BqbzB1YjZHQ3FwcDhvNDVmMG9JbDlmdS9JdjNXcFNNSXFKTXZ1MEVGVWs1VzJQaW5jZGlUaVRtZFdYWlU1bkV2NkgyRFBRQWZYYlNnSmlHSHFLbmJZTUZZM0FTdmRIL0hQaVZBb1RCMk1YZkg0eGJOVTdEbjZtRFNhb2QwbzdHZHJEWDNtODQ1UXBQUVNyUFhHemY0WDkyajhIdlBCSWE4Smcrb0RxbHozUVQ5N2FXUXdYWWU2S0h4emI1ejRITXdEdXEyRDRkYzhoMi9CbW10MzRMelVGUVIyaExSZGRaZ0xkdzF5cHJZdFZwY3dEc1d4UURBTzdOcjV2ZEp4VVR2TVhmODBRSnp1REhXREpTVlJLdDJwWmlpaXhXeGRwRmNod1BzQ3d2aVBqMGwrcWFFWU1jMXNQbENkVkFGem43VXJrSThWbS8wWHlwR2xZb3BSL2FkV0xVekgrbGMrYno1ZEM2SnVLVXdtY1EyRXlZeDZiS0Izbi9YdUViWGdFeU5PMWZTdE1rRlhyWmpvMVpzdlYyUFRjMzMrdEs0ZDhkNkZrdjh5VVR6WHhJRkxIaVNsOUx6VVdtT3BCaWhrTFBCT09jcXkyOHh1UmkzOEM3UFRqMmN6c3RkOUo1VUY0azBJdUdEbVZzM2xjdWg1SEJjYThIeXM2aEpvOG1HbFpMNWN6R2s5bi8vRE1EbDY3RlJraG5QNFNhSDBpZGI5VFEvMERLeFRBTUdhcWpPaEl5ekVqd2ZDQVJleFdlbldyOGlPVkhScDhGM25WZVdvbFRGK002N0xpdi9XNGJXdDk0VEg3b0laUU5lYmZYKzVOKy9Td25Hb1dyMTlWK0pEb2lIRVFLZ1cwMWVuYjZKUXo5Slh2Tm95ZzF3RnJPVmxGc2xwNlRHa1BlN2Rnd2IrWT0ifQ==" ) { posts { id title } nextToken } }
-
Choose Execute query (the orange play button).
-
The remaining posts authored by
AUTHORNAME
should appear in the results pane to the right of the query pane. It should look similar to the following:{ "data": { "allPostsByAuthor": { "posts": [ { "id": "8", "title": "A series of posts, Volume 8" }, { "id": "5", "title": "A series of posts, Volume 5" }, { "id": "3", "title": "A series of posts, Volume 3" }, { "id": "9", "title": "A series of posts, Volume 9" } ], "nextToken": null } } }
Using Sets
Up to this point the Post
type has been a flat key/value object. You can also
model complex objects with the AWS AppSyncDynamoDB resolver, such as sets, lists, and maps.
Let’s update the Post
type to include tags. A post can have 0 or more tags,
which are stored in DynamoDB as a String Set. You’ll also set up some mutations to add and remove
tags, and a new query to scan for posts with a specific tag.
-
Choose the Schema tab.
-
In the Schema pane, modify the
Post
type to add a newtags
field as follows:type Post { id: ID! author: String title: String content: String url: String ups: Int! downs: Int! version: Int! tags: [String!] }
-
In the Schema pane, modify the
Query
type to add a newallPostsByTag
query as follows:type Query { allPostsByTag(tag: String!, count: Int, nextToken: String): PaginatedPosts! allPostsByAuthor(author: String!, count: Int, nextToken: String): PaginatedPosts! allPost(count: Int, nextToken: String): PaginatedPosts! getPost(id: ID): Post }
-
In the Schema pane, modify the
Mutation
type to add newaddTag
andremoveTag
mutations as follows:type Mutation { addTag(id: ID!, tag: String!): Post removeTag(id: ID!, tag: String!): Post deletePost(id: ID!, expectedVersion: Int): Post upvotePost(id: ID!): Post downvotePost(id: ID!): Post updatePost( id: ID!, author: String, title: String, content: String, url: String, expectedVersion: Int! ): 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 allPostsByTag field on the Query type, and then choose Attach.
-
In Data source name, choose PostDynamoDBTable.
-
In Configure the request mapping template, paste the following:
{ "version" : "2017-02-28", "operation" : "Scan", "filter": { "expression": "contains (tags, :tag)", "expressionValues": { ":tag": $util.dynamodb.toDynamoDBJson($context.arguments.tag) } } #if( ${context.arguments.count} ) ,"limit": $util.toJson($context.arguments.count) #end #if( ${context.arguments.nextToken} ) ,"nextToken": $util.toJson($context.arguments.nextToken) #end }
-
In Configure the response mapping template, paste the following:
{ "posts": $utils.toJson($context.result.items) #if( ${context.result.nextToken} ) ,"nextToken": $util.toJson($context.result.nextToken) #end }
-
Choose Save.
-
In the Data types pane on the right, find the newly created addTag field on the Mutation type, and then choose Attach.
-
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" : "ADD tags :tags, version :plusOne", "expressionValues" : { ":tags" : { "SS": [ $util.toJson($context.arguments.tag) ] }, ":plusOne" : { "N" : 1 } } } }
-
In Configure the response mapping template, paste the following:
$utils.toJson($context.result)
-
Choose Save.
-
In the Data types pane on the right, find the newly created removeTag field on the Mutation type, and then choose Attach.
-
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" : "DELETE tags :tags ADD version :plusOne", "expressionValues" : { ":tags" : { "SS": [ $util.toJson($context.arguments.tag) ] }, ":plusOne" : { "N" : 1 } } } }
-
In Configure the response mapping template, paste the following:
$utils.toJson($context.result)
-
Choose Save.
Call the API to Work with Tags
Now that you’ve set up the resolvers, AWS AppSync knows how to translate incoming
addTag
, removeTag
, and allPostsByTag
requests into
DynamoDBUpdateItem
and Scan
operations.
To try it out, let’s select one of the posts you created earlier. For example, let’s use
a post authored by Nadia
.
-
Choose the Queries tab.
-
In the Queries pane, paste the following query:
query allPostsByAuthor { allPostsByAuthor( author: "Nadia" ) { posts { id title } nextToken } }
-
Choose Execute query (the orange play button).
-
All of Nadia’s posts should appear in the results pane to the right of the query pane. It should look similar to the following:
{ "data": { "allPostsByAuthor": { "posts": [ { "id": "10", "title": "The cutest dog in the world" }, { "id": "11", "title": "Did you known...?" } ], "nextToken": null } } }
-
Let’s use the one with the title
"The cutest dog in the world"
. Note down itsid
because you’ll use it later.
Now let’s try adding a dog
tag.
-
In the Queries pane, paste the following mutation. You’ll also need to update the
id
argument to the value you noted down earlier.mutation addTag { addTag(id:10 tag: "dog") { id title tags } }
-
Choose Execute query (the orange play button).
-
The post is updated with the new tag.
{ "data": { "addTag": { "id": "10", "title": "The cutest dog in the world", "tags": [ "dog" ] } } }
You can add more tags as follows:
-
Update the mutation to change the
tag
argument topuppy
.mutation addTag { addTag(id:10 tag: "puppy") { id title tags } }
-
Choose Execute query (the orange play button).
-
The post is updated with the new tag.
{ "data": { "addTag": { "id": "10", "title": "The cutest dog in the world", "tags": [ "dog", "puppy" ] } } }
You can also delete tags:
-
In the Queries pane, paste the following mutation. You’ll also need to update the
id
argument to the value you noted down earlier.mutation removeTag { removeTag(id:10 tag: "puppy") { id title tags } }
-
Choose Execute query (the orange play button).
-
The post is updated and the
puppy
tag is deleted.{ "data": { "addTag": { "id": "10", "title": "The cutest dog in the world", "tags": [ "dog" ] } } }
You can also search for all posts that have a tag:
-
In the Queries pane, paste the following query:
query allPostsByTag { allPostsByTag(tag: "dog") { posts { id title tags } nextToken } }
-
Choose Execute query (the orange play button).
-
All posts that have the
dog
tag are returned as follows:{ "data": { "allPostsByTag": { "posts": [ { "id": "10", "title": "The cutest dog in the world", "tags": [ "dog", "puppy" ] } ], "nextToken": null } } }
Using Lists and Maps
In addition to using DynamoDB sets, you can also use DynamoDB lists and maps to model complex data in a single object.
Let’s add the ability to add comments to posts. This will be modeled as a list of map
objects on the Post
object in DynamoDB.
Note: in a real application, you would model comments in
their own table. For this tutorial, you’ll just add them in the Post
table.
-
Choose the Schema tab.
-
In the Schema pane, add a new
Comment
type as follows:type Comment { author: String! comment: String! }
-
In the Schema pane, modify the
Post
type to add a newcomments
field as follows:type Post { id: ID! author: String title: String content: String url: String ups: Int! downs: Int! version: Int! tags: [String!] comments: [Comment!] }
-
In the Schema pane, modify the
Mutation
type to add a newaddComment
mutation as follows:type Mutation { addComment(id: ID!, author: String!, comment: String!): Post addTag(id: ID!, tag: String!): Post removeTag(id: ID!, tag: String!): Post deletePost(id: ID!, expectedVersion: Int): Post upvotePost(id: ID!): Post downvotePost(id: ID!): Post updatePost( id: ID!, author: String, title: String, content: String, url: String, expectedVersion: Int! ): 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 addComment field on the Mutation type, and then choose Attach.
-
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 comments = list_append(if_not_exists(comments, :emptyList), :newComment) ADD version :plusOne", "expressionValues" : { ":emptyList": { "L" : [] }, ":newComment" : { "L" : [ { "M": { "author": $util.dynamodb.toDynamoDBJson($context.arguments.author), "comment": $util.dynamodb.toDynamoDBJson($context.arguments.comment) } } ] }, ":plusOne" : $util.dynamodb.toDynamoDBJson(1) } } }
This update expression will append a list containing our new comment to the existing
comments
list. If the list doesn’t already exist, it will be created. -
In Configure the response mapping template, paste the following:
$utils.toJson($context.result)
-
Choose Save.
Call the API to Add a Comment
Now that you’ve set up the resolvers, AWS AppSync knows how to translate incoming
addComment
requests into DynamoDBUpdateItem
operations.
Let’s try it out by adding a comment to the same post you added the tags to.
-
Choose the Queries tab.
-
In the Queries pane, paste the following query:
mutation addComment { addComment( id:10 author: "Steve" comment: "Such a cute dog." ) { id comments { author comment } } }
-
Choose Execute query (the orange play button).
-
All of Nadia’s posts should appear in the results pane to the right of the query pane. It should look similar to the following:
{ "data": { "addComment": { "id": "10", "comments": [ { "author": "Steve", "comment": "Such a cute dog." } ] } } }
If you execute the request multiple times, multiple comments will be appended to the list.
Conclusion
In this tutorial, you’ve built an API that lets us manipulate Post objects in DynamoDB using AWS AppSync and GraphQL. For more information, see the Resolver Mapping Template Reference.
To clean up, you can delete the AppSync GraphQL API from the console.
To delete the DynamoDB table and the IAM role you created for this tutorial, you can run
the following to delete the AWSAppSyncTutorialForAmazonDynamoDB
stack, or visit
the AWS CloudFormation console and delete the stack:
aws cloudformation delete-stack \ --stack-name AWSAppSyncTutorialForAmazonDynamoDB