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.

Customize to your needs 🎨 via Photo by russn_fckr on Unsplash

Note: This article was first posted on my blog. I publish each article two weeks later on Medium. You might want to subscribe to my newsletter because you will get content like this earlier! 💌

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 Promise.all.

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

Add Amplify:

yarn add aws-amplify aws-amplify-react

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 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 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 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, which means 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 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 🤗

Helpful Resources

If you want to dig deeper, I can recommend the following resources:

  1. The AWS Amplify docs for custom resolvers. These will teach you how to write resolvers in general.
  2. The ‘AWS AppSync Tutorial: DynamoDB Batch Resolvers’. It shows how to make batch creations using AWS AppSync.
  3. The AWS AppSync docs for the resolver mapping template which explains what the keys in the .vtl files like ”operation” and ”key” do.
  4. 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.


Note: This article was first posted on my blog. I publish each article two weeks later on Medium. You might want to subscribe to my newsletterbecause you will get content like this earlier! 💌


Footnotes

  1. You soon might have these inputs in your schema, too! 🎉
  2. .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.
  3. 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 25 items. You can split the items in chunks of 25 and then call the mutation for each chunk using asyncForEach.