Advanced Features of Appsync and GraphQL within AWS Amplify Framework · open_bits

Advanced Features of Appsync and GraphQL within AWS Amplify Framework

My team at Rivian Automotive has used the AWS Amplify framework for two years now to develop web applications. The framework allows users to easily orchestrate and connect different AWS services (e.g. S3, Appsync, Lambda, Cognito, DynamoDB). We’ve built our main application on a microfrontend architecture, using webpack to federate different microfrontends into a single application. Each time there is a significant new vertical to add to the application, we typically create a new Amplify project to encapsulate the module-specific functionality and business logic (both backend and frontend). We are mainly pleased with the capabilities afforded through AWS Amplify; However, the AWS-provided documentation shows only small, isolated examples of the type of service orchestration and customizability Amplify is capable of. It took us two years of developing with the framework to better understand what was possible inside of it. This blog post is targeted to a developer who has been working with the Amplify framework for a few weeks or months, and is looking to understand how they can better leverage Appsync and the GraphQL Transformer to use focused abstractions that can simplify their application.



In this video, we focus on Appsync because the GraphQL schema served by Appsync specifies the format of the interface between the frontend and the backend. By understanding the tools available to build this interface, you can more precisely specify how the FE should interact with the BE of your application, and thus mitigate communication errors at the interface layer, rather than in your custom lambda resolver or, in the worst case, in your database. Because the Appsync GraphQL schema - along with the CloudFormation specifying the underlying database resources and their resolvers - are generated by the Amplify GraphQL Transformer, you can’t have a thorough understanding of Appsync in the context of Amplify without understanding the transformer. This recording of a meeting shows an example usage of 4 advanced/lesser-known features and capabilities of Appsync inside of AWS Amplify. Many of these features require Amplify GraphQL Transformer 2.0 - a major version upgrade that went live in November 2021. Where the newer version is required to use the feature, the dependency is explicitly called out. The 4 features are:

  1. Overriding Pipeline Resolvers
    With the release of GraphQL API Transformer 2.0, the generated resolvers are pipeline resolvers, which you can read about here: https://docs.aws.amazon.com/appsync/latest/devguide/pipeline-resolvers.html. In the example, I include an additional few fields in the default record created by the Create{type} resolver autogenerated by the GraphQL compiler. More info here: https://docs.amplify.aws/cli/graphql/custom-business-logic/#override-amplify-generated-resolvers

  2. Strongly-Typed Lambda Function Inputs and Outputs
    This “feature” is available in both versions of the graphql transformer, but is not necessarily obvious. In the lambda function resolver documentation all functions have String inputs and String outputs, but it does not have to be that way. By defining an explicit type for the inputs and outputs of your lambda functions, you will give FE developers improved understanding of the function’s interface and improve their ability to utilize the function. It also reduces the amount of code you need to write inside your lambda to decrypt the input payload and confirm that the correct fields have been received. More info here: https://docs.amplify.aws/cli-legacy/graphql-transformer/function/ . You can see in this example how they return a complex Post type from the lambda function or User type. You can do the same with inputs and show clearly which are required vs non required inputs with “!”: Just make sure that you are NOT resolving your function inputs + outputs with objects that are dumped into json Strings; This is a complete misuse of the power of GraphQL and neglects the capacity of AWS Appsync to do your type-checking and schema validation for you.

  3. Split Schema Definitions
    Conventionally, we have defined our schemas inside of the same schema.graphql file in the amplify/backend/api folder in each amplify repo. However, amplify supports splitting the schema definition into many files, which can help to reduce merge conflicts and instances of amplify push overwriting changes from other developers in a development environment. It also helps to clarify where/how each schema element is used and makes it easier to know when to deprecate unused schema definitions. More info here: https://docs.amplify.aws/cli-legacy/graphql-transformer/overview/#rebuild-graphql-api

  4. Custom Resolvers
    These allow you to define and build completely custom resolvers for arbitrary queries or mutations on GraphQL. For reference, the way GraphQL works is: GraphQL API Endpoint to/from Resolver to/from Data Source (good reading here if you need some background). With custom resolvers, you can write your own logic in Velocity/VTL language (*as of November 2022, you can use CDK to write these resolvers in Typescript or Javascript as well). In this demo, we show how we created a resolver that supports one the batch write operations that DynamoDB supports, but the Amplify GraphQL Transformer does not generate a resolver for by default. More info here: https://docs.amplify.aws/cli/graphql/custom-business-logic/#vtl-resolver. You can of course directly connect your Appsync API to a lambda function via the @function directive in Amplify. However, for simple GraphQL operations on a single data source, a lambda function (and the error handling and unit tests you should be writing for it) may be overkill.