Creating GraphQL Batch Operations for AWS Amplify with AppSync and Cognito
In this article, you are going to learn how to create bulk queries and mutations that require authentication for an AWS Amplify project using the AppSync API.
Another 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, first. Amplify’s ‘Getting Started’ is also pretty good.
Grateful note: Huge thank you to Mike for helping me to debug 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
As you can probably tell,
Promise.all can lead to race conditions and other bad problems. What happens if one request throws, but the other comes 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 @Geromekevin. (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.
npx create-react-app amplify-batch-example && cd amplify-batch-example
yarn add aws-amplify aws-amplify-react
Next, initialize Amplify. Add
amplify add <category>). While going through the prompts for
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
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
Note: You need to run
amplify add api without the
batchAddTodos mutation first and then add it via
amplify update api.
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” key in
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 file
And create a file for the response in
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
__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
”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, which means there are no race conditions ⛔️🏎.
Using the Batch Mutation
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
Summary and Closing Comments
We wrote custom resolvers to 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 🎓.
If you liked this article, you might want to clap a bunch of times and and say “Hi” to me on Twitter. Furthermore, check out my other AWS Amplify tutorial: “Email Only Sign Up With AWS Amplify”. Thank you 🤗
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
- The AWS AppSync Docs for the
\$utilreference which explains the methods used by
You can spend hours reading and studying these links, but they will equip you with the knowledge to tackle a variety of problems.
- You soon might have these inputs in your schema, too! 🎉
.vtlstands 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.
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 25 items. You can split the items in chunks of 25 and then call the mutation for each chunk using