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 Amazon AppSync and connect them to a GraphQL API.
You can let Amazon 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 Amazon resources.
-
Provision Amazon resources using the following Amazon 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_IAMAlternatively, you can launch the following Amazon CloudFormation stack in the US-West 2 (Oregon) region in your Amazon account.
This creates the following:
-
A DynamoDB table called
AppSyncTutorial-Postthat will holdPostdata. -
An IAM role and associated IAM managed policy to allow Amazon AppSync to interact with the
Posttable.
-
-
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 Amazon AppSync:
-
Sign in to the Amazon Web Services 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 Amazon 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 Amazon 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 Amazon Web Services 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-PostDynamoDB
table.
First, Amazon AppSync needs to be aware of your tables. You do this by setting up a data source in Amazon AppSync:
-
Sign in to the Amazon Web Services 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:invokeFunctionIAM 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:
-
-
Choose Create.
Setting up the addPost resolver (DynamoDB PutItem)
After Amazon 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
addPostfield on theMutationtype. 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
PostDynamoDBTabledata source you defined earlier, so you can add entries into theAppSyncTutorial-PostDynamoDB 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 Amazon 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
Posttype 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 Amazon Web Services 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:invokeFunctionIAM 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:
-
-
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
authorfield to{ "S" : "${context.arguments.author}" }. TheSpart indicates to Amazon AppSync and DynamoDB that the value will be a string value. The actual value gets populated from theauthorargument. Similarly, theversionfield is a number field because it usesNfor the type. Finally, you’re also initializing theups,downsandversionfield.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. Amazon 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-Posttable exactly matches the shape of thePosttype 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, Amazon 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:
-
Amazon AppSync received an
addPostmutation request. -
Amazon 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 } } } -
Amazon AppSync used the request mapping document to generate and execute a DynamoDB
PutItemrequest. -
Amazon AppSync took the results of the
PutItemrequest 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, Amazon 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:
-
Amazon AppSync received a
getPostquery request. -
Amazon 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" } } } -
Amazon AppSync used the request mapping document to generate and execute a DynamoDB GetItem request.
-
Amazon AppSync took the results of the
GetItemrequest 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 Amazon 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
Mutationtype to add a newupdatePostmutation 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
expressionfield in theupdatesection. It says to set theauthor,title,contentand url attributes, and then increment theversionfield. 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 theexpressionValuesfield. Finally, DynamoDB has reserved words that cannot appear in theexpression. For example,urlis a reserved word, so to update theurlfield you can use name placeholders and define them in theexpressionNamesfield.For more info about
UpdateItemrequest 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, Amazon 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
idargument 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 Amazon AppSync and DynamoDB to do anything with
those fields. Also, the version field was incremented by 1 because you asked
Amazon 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
updatePostfield in theMutationtype to remove the exclamation marks from theauthor,title,content, andurlarguments, making sure to leave theidfield as is. This will make them optional argument. Also, add a new, requiredexpectedVersionargument.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 Amazon AppSync and DynamoDB to update that attribute on the object in DynamoDB. If the attribute is
set to null, it asks Amazon 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 Amazon 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
idargument 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 Amazon 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
versionfield of the post in DynamoDB was2, which matched theexpectedVersionargument. The request succeeded, which meant theversionfield was incremented in DynamoDB to3. -
The second time you ran the request, the value of the
versionfield of the post in DynamoDB was3, which did not match theexpectedVersionargument.
This pattern is typically called optimistic locking.
A feature of an Amazon 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
Mutationtype to add newupvotePostanddownvotePostmutations 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
downvotePostfield 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, Amazon 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
idargument 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
upsandversionfield incrementing by 1 each time you execute the query. -
Change the query to call the
downvotePostmutation 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
downsandversionfield 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
Mutationtype to add a newdeletePostmutation 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
expectedVersionfield 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
expectedVersionargument is an optional argument. If the caller set anexpectedVersionargument in the request, the template adds a condition that only allows theDeleteItemrequest to succeed if the item is already deleted or if theversionattribute of the post in DynamoDB exactly matches theexpectedVersion. If left out, no condition expression is specified on theDeleteItemrequest. 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, Amazon 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
idargument 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 Amazon 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
idof 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
idargument 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
versionof the post in DynamoDB does not match theexpectedValuespecified in the arguments. The current value of the object is returned in thedatafield in theerrorssection 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
Querytype to add a newallPostquery as follows:type Query { allPost(count: Int, nextToken: String): PaginatedPosts! getPost(id: ID): Post } -
Add a new
PaginationPoststype: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 fornextTokencomes 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
allPostquery 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 Amazon AppSync DynamoDB Resolver: the list of posts is calleditemsin the Amazon AppSync DynamoDB Resolver results, but is calledpostsinPaginatedPosts. -
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, Amazon 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
allPostquery to include thenextTokenfrom 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
nextTokenin 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
Querytype to add a newallPostsByAuthorquery 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
PaginatedPoststype that you used with theallPostquery. -
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
allPostresolver, 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 fornextTokencan 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
allPostresolver. -
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, Amazon 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
Nadiashould 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
AUTHORNAMEshould 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
nextTokenargument 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
AUTHORNAMEshould 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 Amazon 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
Posttype to add a newtagsfield 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
Querytype to add a newallPostsByTagquery 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
Mutationtype to add newaddTagandremoveTagmutations 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, Amazon 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 itsidbecause 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
idargument 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
tagargument 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
idargument 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
puppytag 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
dogtag 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
Commenttype as follows:type Comment { author: String! comment: String! } -
In the Schema pane, modify the
Posttype to add a newcommentsfield 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
Mutationtype to add a newaddCommentmutation 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
commentslist. 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, Amazon 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 Amazon 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 Amazon CloudFormation console and delete the stack:
aws cloudformation delete-stack \ --stack-name AWSAppSyncTutorialForAmazonDynamoDB