Friday, 31 May, 2024 UTC


Summary

In this post, we will look at strategies to consider when building out your GraphQL API and explore why AWS AppSync is a great choice for building and deploying this API. We will review best practices that have been devised by the AWS AppSync team and the specialists who use it regularly. Specifically, we will cover how to optimize the security, performance, coding standards, and deployment of your AWS AppSync API.
GraphQL is an alternative to REST and has become a popular query language for APIs and provides a runtime to fulfill both on-demand queries and real-time updates from your existing data sources. It provides understandable descriptions of the data in the API and allows clients to specify exactly which data they wish to receive.
As a fully managed service, AWS AppSync gives you the ability to build and deploy serverless GraphQL and WebSocket APIs that will scale automatically to handle any size of workload. AWS AppSync allows you to create a flexible API to securely access, manipulate, and combine data sources while only paying for the requests to your API and the real-time messages delivered to connected clients. AWS AppSync is tightly coupled with many other AWS services such as Amazon DynamoDB, Amazon RDS, and Amazon Cognito.
Security best practices

Authentication

One important security consideration is the authentication method you will use for your GraphQL APIs. This decision will be based on your specific access requirements, existing infrastructure, and balancing the trade-offs between the complexity of setup and maintenance against adopting a new service.
If your organization already has an identity provider that supports OpenID Connect (OIDC), such as Okta, Auth0, or Microsoft Azure Active Directory, using OIDC with AWS AppSync allows you to leverage your existing user management and authentication infrastructure. This can simplify the integration process and reduce the need to manage separate user databases, however, you will still need to handle the token validation as well as configure and maintain the integration with your chosen identity provider.
If you don’t currently have an identity provider in place, AWS AppSync offers alternative authentication mechanisms such as API keys, Amazon Cognito User Pools, IAM policies, and custom Lambda authorizers.
API Keys are beneficial for publicly accessible applications that do not require individual user authentication. IAM policies are preferred for system-to-system configurations between various AWS services while Lambda authorizers provide a catch-all alternative when highly custom authentication logic is required.
When user authentication is required, Cognito User Pools are the recommended approach. Cognito offers less configuration overhead compared to third party providers and allows you to provide fine-grained access control/filtering based on user attributes, groups or roles. Being a native AWS service, Cognito provides integration with AWS monitoring tools such Amazon CloudTrail and Amazon CloudWatch, which further reduces infrastructure complexity. Cognito is a fully managed service, meaning it automatically scales to handle varying loads and provides high availability. It also handles common user management processes like registration, authentication, and account recovery as well as integrating with social identity providers like Google, Facebook, and Amazon so users can sign in with their existing social media accounts.

Secrets management

If you are using API keys as the authentication method for your GraphQL APIs, one best practice is to regularly rotate the API keys. In the event your API keys are compromised, this will reduce the window of opportunity that the key can be used maliciously. This best practice can become easy to miss, especially if you are using a tool like AWS Amplify to manage all the heavy lifting for you. To implement this in Amplify, set up environment secrets as shown in the Amplify documentation and configure the secret in AWS Systems Manager Parameter Store to reference AWS Secrets Manager. You can then configure the automatic rotation of secrets within Secrets Manager as per the Secrets Manager documentation
You can also store environment variables in GraphQL resolvers and functions for cases where your variable doesn’t store sensitive information. For example, this may be a variable flagging whether the environment is a dev or prod environment. Environment variables are particularly useful for situations where you must reference configuration data that’s only available during the initial setup but needs to be used by your resolvers and functions at runtime. Further details on using environment variables in AWS AppSync can be found in the environment variables documentation.
Performance best practices

Caching

If your API frequently receives the same query from a user, it may be more cost-effective to cache the response to avoid having to repeatedly send a request to your servers. If you need to cache your data, you will need to decide where your caching will take place. You can opt to implement caching at the AWS AppSync resolver level, or at the underlying data source level if available. For example, DynamoDB provides caching via the DynamoDB Accelerator (DAX).
The first and simplest option is to enable caching directly on AWS AppSync resolvers. You can set a time-to-live (TTL) for caching query results from a resolver. This caches the results at the resolver level rather than the data source level. One benefit of resolver caching is that it works across multiple data sources – you can cache results from Lambda resolvers, HTTP resolvers, or DynamoDB resolvers.
The downside of resolver caching is that the cache is specific to AWS AppSync. For example, if you have AWS AppSync and a different API querying a DynamoDB table, only the AWS AppSync resolver will benefit from the cached response. For applications where you have multiple applications reading from the DynamoDB table, caching at the DynamoDB level may be more efficient.
As mentioned before, when using AWS AppSync with DynamoDB, another option to increase performance is to use DynamoDB Accelerator (DAX). This provides in-memory caching for reads against DynamoDB tables which takes pressure off the base DynamoDB service. AWS AppSync calls to DynamoDB resolvers will also be accelerated using DAX.
A key benefit of DAX is it’s handled entirely separately from AWS AppSync. There is no additional configuration required in AWS AppSync to utilize this feature – you just enable DAX on the DynamoDB table powering your AWS AppSync resolvers. DAX also supports clustering for high availability.
One potential downside of DAX is stale data. The DAX cache is eventually consistent with the main DynamoDB table, so if you require strongly consistent reads, you may need to implement additional logic in your AWS AppSync resolvers and use the ConsistentRead parameter for DynamoDB.
For applications where you need to limit the response size, AWS AppSync also offers the ability to compress the API responses. This means responses load faster, content is downloaded faster, and also potentially reduces data transfer costs too.
Coding best practices

Simplifying code

A resolver is a set of functions that define how AppSync uses a GraphQL request to interface with a data source. There are two types of resolvers; unit resolvers and pipeline resolvers. Unit resolvers run directly against a single data source whilst pipeline resolvers contain one or more reusable functions that can be invoked against multiple data sources.
One key development best practice is to avoid repeated code through the use of functions as per the “Don’t Repeat Yourself” principle, also commonly known as the DRY principle. This principle also applies to your resolvers, where you should aim to use pipeline resolvers instead of unit resolvers when you will be reusing code. Another benefit of using pipeline resolvers is the flexibility that they offer which can be useful for cases where you anticipate that the data structure may change.
Maximizing the simplicity of your code can provide significant time savings as fewer lines of code need to be processed by developers when debugging. To this effect, native resolvers are built-in resolvers provided by AWS AppSync that allow you to interact with AWS services directly from your GraphQL API without writing custom resolver code. This can help simplify your code and reduce the margin for error.

Resolver language

When AWS AppSync was initially introduced, resolvers had to be defined using mapping templates written in Apache Velocity Template Language (VTL). Based on customer feedback, AWS AppSync introduced support for the JavaScript language in resolvers and is now the recommended language of choice for AppSync resolvers. Its popularity in the developer community means that there more resources available online for troubleshooting any issues compared to Apache VTL.
Deployment best practices

Deployment pipelines

Before deploying your application, you will need to decide whether you will deploy your application using Amplify or manage the deployment yourself. Using Amplify allows you to get started quicker and abstracts away the complexity of configuring services like Amazon Cognito and Amazon S3. It also simplifies the deployment cycle by offering a fully managed Continuous Integration/Continuous Deployment (CI/CD) pipeline and hosting service that can build, deploy and host your web app in a zero-config way. CI/CD is a best practice as it allows development teams to merge their code changes more frequently into a shared code branch and automate the testing and release of these changes, significantly reducing the time between releases and the risk of deploying bugs to production.
However, if your project requires significant customization compared to what Amplify offers out of the box or if your organization has existing tooling that overlaps with Amplify’s functionality, then deploying the AWS AppSync project yourself may be preferred.

Infrastructure as Code

Whether you opt to deploy your app using Amplify or by yourself, one critical best practice is to use Infrastructure as Code (IaC). This reduces the time for redeploying your application after any changes and also ensures consistency in the infrastructure deployed. AWS Cloud Development Kit (CDK) allows you to use your familiar coding languages like Python, JavaScript, and TypeScript to deploy your infrastructure. Using IaC provides reusable assets for defining your infrastructure and allows infrastructure deployment to be reliably repeated without the risk of human error.
When building an AWS AppSync API, you can opt to use either the native AWS AppSync CDK construct, or the Amplify API CDK construct. The native constructs for AWS AppSync require you to define various elements of the API including the API definition, resolvers, data sources, and the authentication mechanisms. The Amplify CDK construct can be defined with significantly less code as after receiving the schema, the Amplify CDK will automatically generate the underlying resolvers, data sources, authentication mechanisms, and DynamoDB tables for you. Due to this abstraction, the Amplify CDK is preferred in most cases, however, if your API requires data sources other than DynamoDB, then the native CDK construct is recommended.
The AWS AppSync documentation provides a simple example of using AWS CDK to deploy a GraphQL API.
Conclusion
In conclusion, building applications with AWS AppSync requires careful consideration of various factors, including security, performance, coding practices, and deployment strategies. In this post, we covered considerations such as choosing the appropriate authentication method for AWS AppSync, leveraging caching mechanisms like resolver caching or DAX, adhering to coding principles like DRY and using pipeline resolvers, and adopting IaC with AWS CDK. Whether you use AWS Amplify or manage the deployment process manually, these considerations and best practices provide a solid foundation for building scalable applications with AWS AppSync.
For more information on AWS AppSync, please refer to the AWS AppSync Developer Guide.

Ariq Rahman

Ariq Rahman is a Solutions Architect working in the AWS Financial Services team. He has a passion for supporting customers in transforming their business through the power of the cloud.

Ryan Yanchuleff

Ryan is a Senior Specialist Solutions Architect for the Worldwide Specialist Organization. He has a passion for helping startups build new solutions to realize their ideas and has built more than a few startups himself. When he’s not playing with technology, he enjoys spending time with his wife and two kids and working on his next home renovation project.