Creating GraphQL Batch Operations for AWS Amplify
In this article, you are going to learn how to create bulk queries and mutations that require authentication in Cognito for an AWS Amplify project using the AppSync API.
Note: This post is an advanced tutorial. Do you want to learn how to accelerate the creation of your projects using Amplify ๐? I recommend checking out Nader Dabit's free course on egghead to learn the basics. Amplify's 'Getting Started' is also pretty good.
Another Note: Huge thank you to Mike for helping me debugging when I first set this up ๐๐ป.
While building my projects using AWS Amplify, I quickly found myself having to batch create objects. My first instinct was to use Promise.all
.
As you can probably tell, Promise.all
can lead to race conditions and other problems. What happens if one of the requests throws, but the others come through? ๐ค
It turns out since February you can write custom resolvers in AWS Amplify, which enables you to write your own batch actions for the AppSync API. If you aren't familiar with writing resolvers: I'm neither. You will get a sufficient basic feeling for them once you read their code further below. In Germany, there is a saying "A good horse only jumps as high as it has to."
We are going to write a custom resolver for batch creations with authentication. Having learned that, you should be able to do batch queries and updates on your own (e.g. leaving out authentication is strictly less code). If you have any questions, you can always hit me up on Twitter via @janhesters. (You might want to say "Hi" regardless.)
Let's code up an example using React ๐๐ป!
Set Up the Example Project
Create a new React app.
Add Amplify:
Next, initialize Amplify. Add auth
and api
(amplify add <category>
). While going through the prompts for api
, choose Amazon Cognito User Pool
authentication, choose Single object with fields
and edit the schema like this:
Otherwise, go with the defaults for both services. (Remember to run amplify push
.)
Lastly, configure Amplify, so that you can wrap the default export in App.js
with the withAuthenticator
HOC. (Again, if you are still reading and don't understand what you are being asked to do here, check out the materials I linked at the beginning. Remember to come back later to read this article. It's not going away.)
Creating the Batch Mutation
The project is set up, and we are ready to create the batch mutation. First, we need to write a simple mutation in schema.graphql
.
Note: You need to run amplify add api
without the batchAddTodos
mutation first and then add it via amplify add update
.
Tip: You can copy CreateTodoInput
straight from the builds/schema.graphql
folder ๐.ยน
Before we create the custom resolver templates, we need to tell CloudFormation where it can find these custom resources. We can do this by adding a value for the "Resources"
in amplify/api/<your-api-name>/stacks/CustomResources.json
.
We provided CloudFormation with the file paths and the name & type of the mutation for the resolvers. Let's write them now. We will start with the resolver for requests. Create a file amplify/api/<your-api-name>/resolvers/Mutation.batchAddTodos.req.vtl
.ยฒ
And create a file for the response in amplify/api/<your-api-name>/resolvers/Mutation.batchAddTodos.res.vtl
.
Okay, hold on, what's going on here?ยณ ๐ค
We start by looping through all items in the request, checking them for permission and throw if a permission is denied. You can compare this file to amplify/api/<your-api-name>/build/resolvers/Mutation.createTodo.res.vtl
and see that the code generated by the API does this too, but just for a single todo. Subsequently, we loop through all items again. In this loop, we add fields for createdAt
, updatedAt
and __typename
. We also add an auto-generated id. These additions are there to keep the data consistent with the data that gets created by the resolvers that result from the Amplify API. We then push the processed items into a todosdata
array. Finally, we make a BatchPutItem
operation, adding the data to the correct table. Note that to support BatchPutItem
the "version"
number is higher than the one in the createTodo
resolver. Additionally, make sure you use the correct name of your table. You can find it in you AWS console when you search for DynamoDB.
In the response, we have to handle errors for the items whose operations failed. We pass the unprocessed keys back to the caller via the errorInfo
field. Afterwards, we return the response as JSON. The response will look like this:
As you can see we neatly separated the processed from the unprocessed todos in our response. This request is atomic, so there are no race conditions โ๏ธ๐.
Using the Batch Mutation
After running amplify push
again, let's use the new mutation in our app.
It works ๐๐ป. Remember: You can clean up the resources and delete everything by running amplify delete
.
Summary and Closing Comments
We wrote custom resolvers to add batch create items. We eliminated race conditions and instead get back both, the processed items and the unprocessed items along with their respective error message. Keep in mind that BatchPutItem
is limited to 25 items.
I think Amplify is an excellent tool that lets you realize your ideas quickly. It's unfortunate that the CLI can't generate the code for some everyday tasks like these batch requests, yet. I can't wait until it can! Until then we have to take care of these things manually ๐.
Helpful Resources
If you want to dig deeper, I can recommend the following resources:
- The AWS Amplify docs for custom resolvers. These will teach you how to write resolvers in general.
- The 'AWS AppSync Tutorial: DynamoDB Batch Resolvers'. It shows how to make batch creations using AWS AppSync.
- The AWS AppSync docs for the resolver mapping template which explains what the keys in the
.vtl
files like"operation"
and"key"
do. - The AWS AppSync Docs for the $util reference which explains the methods used by
$util
.
You can spend hours reading and studying these links, but they will equip you with the knowledge to tackle a variety of problems.
Footnotes
- You soon might have these inputs in your schema, too! ๐
.vtl
stands for "Velocity Template Language". There is a neat VSCode extension for Velocity you want code highlighting for this language. Here is how it relates to the data sources for AWS AppSync.- Pro tip: You can "
console.log
" by using$util.appendError($util.toJson($ctx.result.data))
in the resolvers.
Bonus
Here is a little code snippet showing asyncForEach
. This function in conjunction with splitEvery(25)
from Ramda can help you chunking your requests if you want to add more than 25 items. You can split the items in chunks of 25 and then call the mutation for each chunk using asyncForEach
.