Creating a simple post application using DynamoDB JavaScript resolvers
In this tutorial, you will import your Amazon DynamoDB tables to Amazon AppSync and connect them to build a fully-functional GraphQL API using JavaScript pipeline resolvers that you can leverage in your own application.
You will use the Amazon AppSync console to provision your Amazon DynamoDB resources, create your resolvers, and connect them to your data sources. You will also be able to read and write to your Amazon DynamoDB database through GraphQL statements and subscribe to real-time data.
There are specific steps that must be completed in order for GraphQL statements to be translated to Amazon 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.
Creating your GraphQL API
To create a GraphQL API in Amazon AppSync
-
Open the AppSync console and choose Create API.
-
Select Design from scratch and choose Next.
-
Name your API
PostTutorialAPI, then choose Next. Skip to the review page while keeping the rest of the options set to their default values and chooseCreate.
The Amazon AppSync console creates a new GraphQL API for you. By detault, it's 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 your GraphQL API, you can set up a basic schema that allows the basic creation, retrieval, and deletion of post data.
To add data to your schema
-
In your API, choose the Schema tab.
-
We will create a schema that defines a
Posttype and an operationaddPostto add and getPostobjects. 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 Schema.
Setting up your Amazon DynamoDB table
The Amazon AppSync console can help provision the Amazon resources needed to store your own resources in an Amazon DynamoDB table. In this step, you’ll create an Amazon DynamoDB table to store your posts. You’ll also set up a secondary index that we’ll use later.
To create your Amazon DynamoDB table
-
On the Schema page, choose Create Resources.
-
Choose Use existing type, then choose the
Posttype. -
In the Additional Indexes section, choose Add Index.
-
Name the index
author-index. -
Set the
Primary keytoauthorand theSortkey toNone. -
Disable Automatically generate GraphQL. In this example, we'll create the resolver ourselves.
-
Choose Create.
You now have a new data source called PostTable, which you can see by visiting Data sources in the side tab. You will use this data source to link your queries
and mutations to your Amazon DynamoDB table.
Setting up an addPost resolver (Amazon DynamoDB PutItem)
Now that Amazon AppSync is aware of the Amazon DynamoDB table, you can link it to individual queries and mutations by
defining resolvers. The first resolver you create is the addPost pipeline resolver using
JavaScript, which enables you to create a post in your Amazon DynamoDB table. A pipeline 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
createPostfield on theMutationtype. This resolver will be invoked when the caller calls mutation{ addPost(...){...} }. -
The data source to use for this resolver. In this case, you want to use the DynamoDB data source you defined earlier, so you can add entries into the
post-table-for-tutorialDynamoDB table. -
The request handler. The request handler is a function that handles the incoming request from the caller and translates it into instructions for Amazon AppSync to perform against DynamoDB.
-
The response handler. The job of the response handler is to handle 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 your resolver
-
In your API, choose the Schema tab.
-
In the Resolvers pane, find the
addPostfield under theMutationtype, then choose Attach. -
Choose your data source, then choose Create.
-
In your code editor, replace the code with this snippet:
import { util } from '@aws-appsync/utils' import * as ddb from '@aws-appsync/utils/dynamodb' export function request(ctx) { const item = { ...ctx.arguments, ups: 1, downs: 0, version: 1 } const key = { id: ctx.args.id ?? util.autoId() } return ddb.put({ key, item }) } export function response(ctx) { return ctx.result } -
Choose Save.
Note
In this code, you use the DynamoDB module utils that allow you to easily create DynamoDB requests.
Amazon AppSync comes with a utility for automatic ID generation called util.autoId(), which is used
to generate an ID for your new post. If you do not specify an ID, the utility will automatically generate it
for you.
const key = { id: ctx.args.id ?? util.autoId() }
For more information about the utilities available for JavaScript, see JavaScript
runtime features for resolvers and functions
Call the API to add a post
Now that the resolver has been configured, Amazon AppSync can translate an incoming addPost
mutation to an Amazon DynamoDB PutItem operation. You can now run a mutation to put something in
the table.
To run the operation
-
In your API, choose the Queries tab.
-
In the Queries pane, add 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 Run (the orange play button), then choose
addPost. The results of the newly created post should appear in the Results pane to the right of the Queries 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 } } }
The following explanation shows what occurred:
-
Amazon AppSync received an
addPostmutation request. -
Amazon AppSync executes the request handler of the resolver. The
ddb.putfunction creates aPutItemrequest that looks like this:{ operation: 'PutItem', key: { id: { S: '123' } }, attributeValues: { downs: { N: 0 }, author: { S: 'AUTHORNAME' }, ups: { N: 1 }, title: { S: 'Our first post!' }, version: { N: 1 }, content: { S: 'This is our first post.' }, url: { S: 'https://aws.amazon.com/appsync/' } } } -
Amazon AppSync uses this value to generate and execute a Amazon 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 } -
The response handler returns the result immediately (
return ctx.result). -
The final result is visible in the GraphQL response.
Setting up the getPost resolver (Amazon DynamoDB GetItem)
Now that you’re able to add data to the Amazon DynamoDB table, you need to set up the getPost query
so it can retrieve that data from the table. To do this, you set up another resolver.
To add your resolver
-
In your API, choose the Schema tab.
-
In the Resolvers pane on the right, find the
getPostfield on theQuerytype and then choose Attach. -
Choose your data source, then choose Create.
-
In the code editor, replace the code with this snippet:
import * as ddb from '@aws-appsync/utils/dynamodb' export function request(ctx) { return ddb.get({ key: { id: ctx.args.id } }) } export const response = (ctx) => ctx.result -
Save your resolver.
Note
In this resolver, we use an arrow function expression for the response handler.
Call the API to get a post
Now that the resolver has been set up, Amazon AppSync knows how to translate an incoming getPost
query to an Amazon DynamoDB GetItem operation. You can now run a query to retrieve the post you
created earlier.
To run your query
-
In your API, choose the Queries tab.
-
In the Queries pane, add the following code, and use the id that you copied after creating your post:
query getPost { getPost(id: "123") { id author title content url ups downs version } } -
Choose Run (the orange play button), then choose
getPost. The results of the newly created post should appear in the Results pane to the right of the Queries pane. -
The post retrieved from Amazon DynamoDB should appear in the Results pane to the right of the Queries 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 } } }
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 function 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 function may look like the snippet below:
import * as ddb from '@aws-appsync/utils/dynamodb' export function request(ctx) { return ddb.get({ key: { id: ctx.args.id }, projection: ['author', 'id', 'title'], }) } export const response = (ctx) => ctx.result
You can also use a selectionSetList with getPost to represent the expression:
import * as ddb from '@aws-appsync/utils/dynamodb' export function request(ctx) { const projection = ctx.info.selectionSetList.map((field) => field.replace('/', '.')) return ddb.get({ key: { id: ctx.args.id }, projection }) } export const response = (ctx) => ctx.result
Create an updatePost mutation (Amazon DynamoDB UpdateItem)
So far, you can create and retrieve Post objects in Amazon DynamoDB. Next, you’ll set up a new
mutation to update an object. Compared to the addPost mutation that requires all fields to be
specified, this mutation allows you to only specify the fields that you want to change. It also introduced a
new expectedVersion argument that allows you to specify the version that you want to modify.
You’ll set up a condition that makes sure that you are modifying the latest version of the object. You’ll do
this using the UpdateItem Amazon DynamoDB operation.sc
To update your resolver
-
In your API, 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, expectedVersion: Int! ): Post addPost( id: ID author: String! title: String! content: String! url: String! ): Post! } -
Choose Save Schema.
-
In the Resolvers pane on the right, find the newly created
updatePostfield on theMutationtype, then choose Attach. Create your new resolver using the snippet below:import { util } from '@aws-appsync/utils'; import * as ddb from '@aws-appsync/utils/dynamodb'; export function request(ctx) { const { id, expectedVersion, ...rest } = ctx.args; const values = Object.entries(rest).reduce((obj, [key, value]) => { obj[key] = value ?? ddb.operations.remove(); return obj; }, {}); return ddb.update({ key: { id }, condition: { version: { eq: expectedVersion } }, update: { ...values, version: ddb.operations.increment(1) }, }); } export function response(ctx) { const { error, result } = ctx; if (error) { util.appendError(error.message, error.type); } return result; -
Save any changes you made.
This resolver uses ddb.update to create an Amazon DynamoDB UpdateItem request. Instead
of writing the entire item, you’re just asking Amazon DynamoDB to update certain attributes. This is done using
Amazon DynamoDB update expressions.
The ddb.update function takes a key and an update object as arguments. Then, you check the
values of the incoming arguments. When a value is set to null, use the DynamoDB
remove operation to signal that the value should be removed from the DynamoDB item.
There is also a new condition section. A condition expression allows you tell Amazon AppSync and
Amazon DynamoDB whether or not the request should succeed based on the state of the object already in Amazon 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 Amazon DynamoDB matches the
expectedVersion argument exactly. When the item is updated, we want to increment the value
of the version. This is easy to do with the operation function increment.
For more information about condition expressions, see the Condition expressions documentation.
For more info about the UpdateItem request, see the UpdateItem documentation and the DynamoDB
module documentation.
For more information about how to write update expressions, see the DynamoDB UpdateExpressions documentation.
Call the API to update a post
Let’s try updating the Post object with the new resolver.
To update your object
-
In your API, choose the Queries tab.
-
In the Queries pane, add 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: 1 ) { id author title content url ups downs version } } -
Choose Run (the orange play button), then choose
updatePost. -
The updated post in Amazon DynamoDB should appear in the Results pane to the right of the Queries 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": 2 } } }
In this request, you asked Amazon AppSync and Amazon DynamoDB to update the title and
content fields only. All of the other fields were left 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 while leaving the request exactly as is. You should see a response similar to the
following:
{ "data": { "updatePost": null }, "errors": [ { "path": [ "updatePost" ], "data": null, "errorType": "DynamoDB:ConditionalCheckFailedException", "errorInfo": null, "locations": [ { "line": 2, "column": 3, "sourceName": null } ], "message": "The conditional request failed (Service: DynamoDb, Status Code: 400, Request ID: 1RR3QN5F35CS8IV5VR4OQO9NNBVV4KQNSO5AEMVJF66Q9ASUAAJG)" } ] }
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 Amazon DynamoDB was1, which matched theexpectedVersionargument. The request succeeded, which meant theversionfield was incremented in Amazon DynamoDB to2. -
The second time you ran the request, the value of the
versionfield of the post in Amazon DynamoDB was2, which did not match theexpectedVersionargument.
This pattern is typically called optimistic locking.
Create vote mutations (Amazon DynamoDB UpdateItem)
The Post type contains ups and downs fields to enable the recording
of upvotes and downvotes. However, at this moment, the API doesn’t let us do anything with them. Let’s add a
mutation to let us upvote and downvote the posts.
To add your mutation
-
In your API, choose the Schema tab.
-
In the Schema pane, modify the
Mutationtype and add theDIRECTIONenum to add new vote mutations:type Mutation { vote(id: ID!, direction: DIRECTION!): Post updatePost( id: ID!, author: String, title: String, content: String, url: String, expectedVersion: Int! ): Post addPost( id: ID, author: String!, title: String!, content: String!, url: String! ): Post! } enum DIRECTION { UP DOWN } -
Choose Save Schema.
-
In the Resolvers pane on the right, find the newly created
votefield on theMutationtype, and then choose Attach. Create a new resolver by creating and replacing the code with the following snippet:import * as ddb from '@aws-appsync/utils/dynamodb'; export function request(ctx) { const field = ctx.args.direction === 'UP' ? 'ups' : 'downs'; return ddb.update({ key: { id: ctx.args.id }, update: { [field]: ddb.operations.increment(1), version: ddb.operations.increment(1), }, }); } export const response = (ctx) => ctx.result; -
Save any changes you made.
Call the API to upvote or downvote a post
Now that the new resolvers have been set up, Amazon AppSync knows how to translate an incoming
upvotePost or downvote mutation to an Amazon DynamoDB UpdateItem
operation. You can now run mutations to upvote or downvote the post you created earlier.
To run your mutation
-
In your API, choose the Queries tab.
-
In the Queries pane, add the following mutation. You’ll also need to update the
idargument to the value you noted down earlier:mutation votePost { vote(id:123, direction: UP) { id author title content url ups downs version } } -
Choose Run (the orange play button), then choose
votePost. -
The updated post in Amazon DynamoDB should appear in the Results pane to the right of the Queries pane. It should look similar to the following:
{ "data": { "vote": { "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 Run a few more times. You should see the
upsandversionfields incrementing by1each time you execute the query. -
Change the query to call it with a different
DIRECTION.mutation votePost { vote(id:123, direction: DOWN) { id author title content url ups downs version } } -
Choose Run (the orange play button), then choose
votePost.This time, you should see the
downsandversionfields incrementing by1each time you run the query.
Setting up a deletePost resolver (Amazon DynamoDB DeleteItem)
Next, you'll want to create a mutation to delete a post. You’ll do this using the DeleteItem
Amazon DynamoDB operation.
To add your mutation
-
In your schema, choose the Schema tab.
-
In the Schema pane, modify the
Mutationtype to add a newdeletePostmutation:type Mutation { deletePost(id: ID!, expectedVersion: Int): Post vote(id: ID!, direction: DIRECTION!): Post updatePost( id: ID!, author: String, title: String, content: String, url: String, expectedVersion: Int! ): Post addPost( id: ID author: String!, title: String!, content: String!, url: String! ): Post! } -
This time, you made the
expectedVersionfield optional. Next, choose Save Schema. -
In the Resolvers pane on the right, find the newly created
deletefield in theMutationtype, then choose Attach. Create a new resolver using the following code:import { util } from '@aws-appsync/utils' import { util } from '@aws-appsync/utils'; import * as ddb from '@aws-appsync/utils/dynamodb'; export function request(ctx) { let condition = null; if (ctx.args.expectedVersion) { condition = { or: [ { id: { attributeExists: false } }, { version: { eq: ctx.args.expectedVersion } }, ], }; } return ddb.remove({ key: { id: ctx.args.id }, condition }); } export function response(ctx) { const { error, result } = ctx; if (error) { util.appendError(error.message, error.type); } return result; }Note
The
expectedVersionargument is an optional argument. If the caller set anexpectedVersionargument in the request, the request handler adds a condition that only allows theDeleteItemrequest to succeed if the item is already deleted or if theversionattribute of the post in Amazon DynamoDB exactly matches theexpectedVersion. If left out, no condition expression is specified on theDeleteItemrequest. It succeeds regardless of the value ofversionor whether or not the item exists in Amazon DynamoDB.Even though you’re deleting an item, you can return the item that was deleted, if it was not already deleted.
For more info about the DeleteItem request, see the DeleteItem documentation.
Call the API to delete a post
Now that the resolver has been set up, Amazon AppSync knows how to translate an incoming delete
mutation to an Amazon DynamoDB DeleteItem operation. You can now run a mutation to delete
something in the table.
To run your mutation
-
In your API, choose the Queries tab.
-
In the Queries pane, add 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 Run (the orange play button), then choose
deletePost. -
The post is deleted from Amazon DynamoDB. Note that Amazon AppSync returns the value of the item that was deleted from Amazon DynamoDB, which should appear in the Results pane to the right of the Queries 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
deletePostis the one that actually deletes it from Amazon DynamoDB. Choose Run 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, 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, add 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 Run (the orange play button), then choose
addPost. -
The results of the newly created post should appear in the Results pane to the right of the Queries pane. Record the
idof the newly created object because you'll 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 with an illegal value for expectedVersion. In the Queries pane, add 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 Run (the orange play button), then choose
deletePost. The following result is returned:{ "data": { "deletePost": null }, "errors": [ { "path": [ "deletePost" ], "data": null, "errorType": "DynamoDB:ConditionalCheckFailedException", "errorInfo": null, "locations": [ { "line": 2, "column": 3, "sourceName": null } ], "message": "The conditional request failed (Service: DynamoDb, Status Code: 400, Request ID: 7083O037M1FTFRK038A4CI9H43VV4KQNSO5AEMVJF66Q9ASUAAJG)" } ] } -
The request failed because the condition expression evaluates to
false. The value forversionof the post in Amazon DynamoDB doesn't 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 theexpectedVersion:mutation deletePost { deletePost( id:123 expectedVersion: 1 ) { id author title content url ups downs version } } -
Choose Run (the orange play button), then choose
deletePost.This time the request succeeds, and the value that was deleted from Amazon 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 Run again. The call still succeeds, but this time no value is returned because the post was already deleted in Amazon DynamoDB.
{ "data": { "deletePost": null } }
Setting up an allPost resolver (Amazon 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.
To add your mutation
-
In your API, choose the Schema tab.
-
In the Schema pane, modify the
Querytype to add a newallPostquery as follows:type Query { allPost(limit: Int, nextToken: String): PaginatedPosts! getPost(id: ID): Post } -
Add a new
PaginationPoststype:type PaginatedPosts { posts: [Post!]! nextToken: String } -
Choose Save Schema.
-
In the Resolvers pane on the right, find the newly created
allPostfield in theQuerytype, then choose Attach. Create a new resolver with the following code:import * as ddb from '@aws-appsync/utils/dynamodb'; export function request(ctx) { const { limit = 20, nextToken } = ctx.arguments; return ddb.scan({ limit, nextToken }); } export function response(ctx) { const { items: posts = [], nextToken } = ctx.result; return { posts, nextToken }; }This resolver's request handler expects two optional arguments:
-
limit- Specifies the maximum number of items to return in a single call. -
nextToken- Used to retrieve the next set of results (we’ll show where the value fornextTokencomes from later).
-
-
Save any changes made to your resolver.
For more information about Scan request, see the Scan reference documentation.
Call the API to scan all posts
Now that the resolver has been set up, Amazon AppSync knows how to translate an incoming allPost
query to an Amazon DynamoDB Scan 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.
To add and query data
-
In your API, choose the Queries tab.
-
In the Queries pane, add 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 Run (the orange play button).
-
Now, let’s scan the table, returning five results at a time. In the Queries pane, add the following query:
query allPost { allPost(limit: 5) { posts { id title } nextToken } } -
Choose Run (the orange play button), then choose
allPost.The first five posts should appear in the Results pane to the right of the Queries 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": "<token>" } } } -
You received five results and a
nextTokenthat you can use to get the next set of results. Update theallPostquery to include thenextTokenfrom the previous set of results:query allPost { allPost( limit: 5 nextToken: "<token>" ) { posts { id author } nextToken } } -
Choose Run (the orange play button), then choose
allPost.The remaining four posts should appear in the Results pane to the right of the Queries 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 an allPostsByAuthor resolver(Amazon DynamoDB Query)
In addition to scanning Amazon DynamoDB for all posts, you can also query Amazon DynamoDB to retrieve posts created by
a specific author. The Amazon DynamoDB table you created earlier already has a GlobalSecondaryIndex
called author-index that you can use with an Amazon DynamoDB Query operation to retrieve
all posts created by a specific author.
To add your query
-
In your API, choose the Schema tab.
-
In the Schema pane, modify the
Querytype to add a newallPostsByAuthorquery as follows:type Query { allPostsByAuthor(author: String!, limit: Int, nextToken: String): PaginatedPosts! allPost(limit: Int, nextToken: String): PaginatedPosts! getPost(id: ID): Post }Note that this uses the same
PaginatedPoststype that you used with theallPostquery. -
Choose Save Schema.
-
In the Resolvers pane on the right, find the newly created
allPostsByAuthorfield on theQuerytype, and then choose Attach. Create a resolver using the snippet below:import * as ddb from '@aws-appsync/utils/dynamodb'; export function request(ctx) { const { limit = 20, nextToken, author } = ctx.arguments; return ddb.query({ index: 'author-index', query: { author: { eq: author } }, limit, nextToken, }); } export function response(ctx) { const { items: posts = [], nextToken } = ctx.result; return { posts, nextToken }; }Like the
allPostresolver, this resolver has two optional arguments:-
limit- Specifies the maximum number of items to return in a single call. -
nextToken- Retrieves the next set of results (the value fornextTokencan be obtained from a previous call).
-
-
Save any changes made to your resolver.
For more information about the Query request, see the Query reference documentation.
Call the API to query all posts by author
Now that the resolver has been set up, Amazon AppSync knows how to translate an incoming
allPostsByAuthor mutation to a DynamoDB Query operation against the
author-index index. You can now query the table to retrieve all the posts by a specific
author.
Before this, however, let’s populate the table with some more posts, because every post so far has the same author.
To add data and query
-
In your API, choose the Queries tab.
-
In the Queries pane, add 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 Run (the orange play button), then choose
addPost. -
Now, let’s query the table, returning all posts authored by
Nadia. In the Queries pane, add the following query:query allPostsByAuthor { allPostsByAuthor(author: "Nadia") { posts { id title } nextToken } } -
Choose Run (the orange play button), then choose
allPostsByAuthor. All posts authored byNadiashould appear in the Results pane to the right of the Queries 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
Queryjust the same as it does forScan. For example, let’s look for all posts byAUTHORNAME, getting five at a time. -
In the Queries pane, add the following query:
query allPostsByAuthor { allPostsByAuthor( author: "AUTHORNAME" limit: 5 ) { posts { id title } nextToken } } -
Choose Run (the orange play button), then choose
allPostsByAuthor. All posts authored byAUTHORNAMEshould appear in the Results pane to the right of the Queries 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": "<token>" } } } -
Update the
nextTokenargument with the value returned from the previous query as follows:query allPostsByAuthor { allPostsByAuthor( author: "AUTHORNAME" limit: 5 nextToken: "<token>" ) { posts { id title } nextToken } } -
Choose Run (the orange play button), then choose
allPostsByAuthor. The remaining posts authored byAUTHORNAMEshould appear in the Results pane to the right of the Queries 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 your resolver, such as sets, lists, and maps. Let’s update the Post type to
include tags. A post can have zero 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.
To set up your data
-
In your API, 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!, limit: Int, nextToken: String): PaginatedPosts! allPostsByAuthor(author: String!, limit: Int, nextToken: String): PaginatedPosts! allPost(limit: 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 Schema.
-
In the Resolvers pane on the right, find the newly created
allPostsByTagfield on theQuerytype, and then choose Attach. Create your resolver using the snippet below:import * as ddb from '@aws-appsync/utils/dynamodb'; export function request(ctx) { const { limit = 20, nextToken, tag } = ctx.arguments; return ddb.scan({ limit, nextToken, filter: { tags: { contains: tag } } }); } export function response(ctx) { const { items: posts = [], nextToken } = ctx.result; return { posts, nextToken }; } -
Save any changes you've made to your resolver.
-
Now, do the same for the
MutationfieldaddTagusing the snippet below:Note
Though the DynamoDB utils currently don't support set operations, you can still interact with sets by building the request yourself.
import { util } from '@aws-appsync/utils' export function request(ctx) { const { id, tag } = ctx.arguments const expressionValues = util.dynamodb.toMapValues({ ':plusOne': 1 }) expressionValues[':tags'] = util.dynamodb.toStringSet([tag]) return { operation: 'UpdateItem', key: util.dynamodb.toMapValues({ id }), update: { expression: `ADD tags :tags, version :plusOne`, expressionValues, }, } } export const response = (ctx) => ctx.result -
Save any changes made to your resolver.
-
Repeat this one more time for the
MutationfieldremoveTagusing the snippet below:import { util } from '@aws-appsync/utils'; export function request(ctx) { const { id, tag } = ctx.arguments; const expressionValues = util.dynamodb.toMapValues({ ':plusOne': 1 }); expressionValues[':tags'] = util.dynamodb.toStringSet([tag]); return { operation: 'UpdateItem', key: util.dynamodb.toMapValues({ id }), update: { expression: `DELETE tags :tags ADD version :plusOne`, expressionValues, }, }; } export const response = (ctx) => ctx.resultexport -
Save any changes made to your resolver.
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 DynamoDB UpdateItem
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.
To use tags
-
In your API, choose the Queries tab.
-
In the Queries pane, add the following query:
query allPostsByAuthor { allPostsByAuthor( author: "Nadia" ) { posts { id title } nextToken } } -
Choose Run (the orange play button), then choose
allPostsByAuthor. -
All of Nadia’s posts should appear in the Results pane to the right of the Queries 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. Record its
idbecause you’ll use it later. Now, let’s try adding adogtag. -
In the Queries pane, add 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 Run (the orange play button), then choose
addTag. 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. Update the mutation to change the
tagargument topuppy:mutation addTag { addTag(id:10 tag: "puppy") { id title tags } } -
Choose Run (the orange play button), then choose
addTag. 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, add 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 Run (the orange play button), then choose
removeTag. The post is updated and thepuppytag 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, add the following query:
query allPostsByTag { allPostsByTag(tag: "dog") { posts { id title tags } nextToken } } -
Choose Run (the orange play button), then choose
allPostsByTag. All posts that have thedogtag are returned as follows:{ "data": { "allPostsByTag": { "posts": [ { "id": "10", "title": "The cutest dog in the world", "tags": [ "dog", "puppy" ] } ], "nextToken": null } } }
Conclusion
In this tutorial, you’ve built an API that lets you manipulate Post objects in DynamoDB using
Amazon AppSync and GraphQL.
To clean up, you can delete the Amazon AppSync GraphQL API from the console.
To delete the role associated with your DynamoDB table, select your data source in the Data Sources table and click edit. Note the value of the role under Create or use an existing role. Go to the IAM console to delete the role.
To delete your DynamoDB table, click on the name of the table in the data sources list. This takes you to the DynamoDB console where you can delete the table.