Improved developer experience of GraphQL APIs with JavaScript resolvers for AWS AppSync APIs

Learn how the developer experience of AppSync APIs has improved with the support of JavaScript resolvers.

AWS Admin
Amazon Employee
Published Oct 30, 2023
Last Modified Jun 21, 2024
AWS AppSync is a fully-managed serverless GraphQL API which enables serverless developers to build, deploy and also a powerful API layer for integrating seamlessly with other AWS services. Clearly, GraphQL is destined to be the future of APIs and learn about how you can get started with AppSync. In this blog, we will look into how AWS AppSync supporting JavaScript resolvers has improved the developer experience.

AppSync resolvers

AppSync resolvers are the components responsible for integrating with different supported AppSync data sources such as - AWS Lambda functions, Amazon Aurora, Amazon DynamoDB, HTTP endpoints and many others. These resolvers have mapping templates for the GraphQL type, query, mutation and subscription. Resolvers can be written in Velocity Template Language (VTL) or JavaScript runtime.
AppSync components with AppSync resolvers

Why are resolvers important?

Whenever integrating with AWS Services, resolvers (VTL or JavaScript based) would help with data manipulation for constructing the request mapping which the data source requires along with the response mapping where the response is constructed to the needed GraphQL schema. Additionally, resolvers support sharing of data between different resolver functions in a pipeline resolver with context and arguments. In both unit resolver and pipeline resolver, AppSync supports usage of different utility functions for data transformation of JSONs, Arrays Lists and generation of UUIDs.
Screenshot of a AppSync Pipeline resolver
The above screenshot from AppSync console shows how a Pipeline resolver can have multiple functions which can integrate with the respective data source and sharing the data amongst the different resolver functions using context.stash.

VTL resolvers

Velocity Template Language (VTL) is used to generate dynamic JSON content and based on Java environment.
For instance, the AppSync model PrivateNote.
A mutation createPrivateNote for creating new notes, in a typical VTL resolver which sets the different default values and validates for authentication of the user with Amazon Cognito for identifying the owner of the note by using the util functions on AppSync - $util.defaultIfNull(), $$util.dynamodb.toDynamoDBJson() and $$util.dynamodb.toMapValues() to make the DynamoDB operation of PutItem.
VTL could be powerful but at certain scenarios, just isn't developer friendly for a developer who is building the Serverless GraphQL API.

Debugging VTL resolvers

In a complex unit resolver or pipeline resolver, debugging is tedious as the VTL code is executed at runtime and having debugging breakpoints or ways to track the flow of execution is hard.
It's possible to add debug logs using the util functions $util.log.info() and $util.log.error() which are available in CloudWatch logs as the execution happens. However, this is not an efficient way of debugging during development.
A good way to catch the errors is possible in the response mapping template.

VTL verbose syntax

When working with if-else statements or foreach looping statement on VTL, the syntax of the VTL can be verbose.

Complexity of the resolver

With the syntax and complex resolvers, often the direct data source to a database or HTTP can become hard to handle with the limited utility handler functions and in case of pipeline resolver, it can be a huge learning curve of how to use different functions, mapping request/response for all the functions and the parent resolver.
For some use-cases and scenarios, using VTL resolvers may turn out to be overwhelming which results in developers moving towards Lambda function resolvers.

JavaScript resolvers

AppSync's JavaScript resolvers enable developers to use JavaScript runtime for all the resolvers instead of VTL.
Using the same example of createPrivateNote mutation, the resolver for it would be something like -
This defines the request() and response() methods for mapping with AppSync's mutation. With the familiarity of JavaScript, it's easy to modularize your resolvers with different JavaScript functions as you see in the example above, dynamodbPutRequest() is invoked from the request() method and whenever this returns, the response() method would handle the response mapping with the defined schema.
In JavaScript resolvers, you can import @aws-appsync/utils for all the util helper methods such as util.transform.toDynamoDBConditionExpression() and util.dynamodb.toMapValues().

A closer look at the developer experience

Based on the stats and popularity of Node.JS usage for Serverless, developers would be familiar with JavaScript runtimes which are used in their existing workloads with Amazon Lambda functions or their Infrastructure as Code (IaC) where NodeJS and TypeScript have been widely adapted. And ensuring that their resolvers could now also be built with JavaScripts, this new enhancement is a celebration for Serverless developers.

Comfort and familiarity with the tech stack

Everyone loves to build with something that they are familiar with. As a developer who has used VTL and now played around with JavaScript resolvers, choosing JavaScript resolvers and building AppSync APIs is a faster and easier choice. Since developers have been used to JavaScript in their backend and also frontend, the learning curve to adapt VTL is reduced massively.

Utilities and functions

JavaScript resolvers also supports utility helper functions with @aws-appsync/utils the package.
  • @aws-appsync/eslint-plugin is the ESLint tool which detects issues in code during the development.
  • Using util.transform helper functions for easier transformations with DynamoDB and other data sources with different filters on Maps and Lists.
  • Built-in modules for different data sources makes it programming language compatible for developers to use functions and operations.
  • Apart from the native JavaScript's flexibility to work with datetime, there are util.time helper functions available.
  • Working with different type classes - Array, String, Object from JavaScript.

Going Type safe with TypeScript

Since JavaScript is supported, there are workarounds available for TypeScript to resolver more type strict.
The catch here is that, AppSync doesn't directly support TypeScript so it's possible to build AppSync APIs only from IDE given that you have the configurations set for building them as JavaScript code.

Code bundling with esbuild

JavaScript resolvers support usage of custom and external libraries but there are some catch with JS runtime requirements on AppSync. While bundling with esbuild, it's possible to bundle with external libraries. Keep in mind that @aws-appsync/* library is already available in JS runtime and this should not be bundled with the code.

Codebase with developer tools

AppSync is deployed with AWS CDK and AWS Amplify which are developer friendly tools for building and deploying AppSync to the cloud, these already support JavaScript and TypeScript. Building AppSync resolvers will make the codebase more organized with the same tech stack. While developing, IDE support various extensions and tools for JavaScript making the code authoring process more smooth for development and debugging.

What's best for you

Choosing what's best for you is really important. JavaScript with no doubt has the better experience while building GraphQL APIs on AppSync when compared with VTL. Some of the patterns that I've seen is that for simpler resolvers such as using Lambda function as the data source, still makes sense to use VTL as in that case VTL is only forwarding all the context arguments to Lambda function.

Any opinions in this post are those of the individual author and may not reflect the opinions of AWS.