Terraform // AppSync // Lambda

Tutorial on wiring Lambdas to an AppSync GraphQL API using Terraform

Adam Bailey
topanga.io
5 min readJan 4, 2021

--

AWS AppSync is a managed service for developing GraphQL APIs. We’ve come to know it over the past few months and it has earned its place as a core part of our platform. It easily integrates with AWS managed data stores, contains multiple and fine grained authorization methods to cover client and back end system needs, and when used with AWS Amplify gets your front end clients up and interacting with your GraphQL back end pretty painlessly.

Terraform is an open source infrastructure as code tool originally developed by HashiCorp. It has been our choice for building and maintaining infrastructure for quite some time and was an obvious choice for us to standardize how we code it all up.

What follows is an examination of a pattern we’ve found ourselves routinely returning to over the course of our development: wiring Lambdas to AppSync with Terraform.

Where are we going?

We’re going to create an AppSync API that offers a hello world query that is backed by a Lambda. More specifically, executing a query against the helloWorldLambda field with a greeting of “hello” will trigger a Lambda to return a response of “world”.

Here is what doing so will look like if you use the handy AppSync query executor in the AWS Portal:

Setting up AppSync with Terraform

The whole example can be found on GitHub. We’ll start with the Terraform resource definitions for our AppSync GraphQL API.

The first resource block is our AppSync API definition. Of note is the default authentication method which in our case is IAM. The other authentication methods that are available are token based and Cognito User Pool based. We’ve found IAM is a good choice for back end systems that need to access your API. Tokens are fine for development, but not great for production — most practically because they necessitate an expiration date which would likely sneak up on you. Cognito User Pools are great for client applications which are beyond the scope of this article.

There are additional logging and permissions resources required to get this example working that are excluded here, but can be found on GitHub.

The second snippet above is our GraphQL schema which we maintain in a separate file and reference in the AppSync API resource block. In this example we are going to work through a very simple hello world example where our API offers a query method that takes one argument named “greeting” and returns a response object.

Connecting a Resolver

Now that we have created our API with our example helloWorldLamba field we need to attach a resolver so that when we query the field there is some useful, or at least demonstrative, logic behind it. To do so we are are going to create two more resources: an AppSync Function and an AppSync Pipeline Resolver. Again, there are a requisite slew of permissioning resources that are excluded for brevity but can be found here.

The first resource above is the AppSync Function that is associated with the AppSync API we looked at previously. The data_source field references a lambda data source that we’ll detail in the next section. The request_mapping_template specifies the operation and payload we want to invoke our target lambda with. When called, $ctx.args.greeting is interpolated with the value of the “greeting” argument. The response_mapping_template specifies what we want to do with the response from the Lambda — here we are simply passing it through.

The AppSync Resolver resource, or the second resource above, connects the AppSync Function to our helloWorldLambda field in our GraphQL Schema. Here the pipeline contains the single function, but could contain multiple functions where results are passed through sequentially. The request and response templates for the resolver are nothing more than pass throughs because all the work is done in the function. Within the function $context.result references the result of the function and within the resolver $ctx.prev.results (which could be written $context.prev.result) references the result of the last function in the pipeline.

You can think of AppSync Functions as units of work that are not linked to a particular field in your GraphQL Schema. As such, they can be used across multiple fields and with some care you can achieve a more functional style for your resolvers. Because we often reuse functions across resolvers, we tend to default to pairing functions with resolvers even if the same result could be achieved by wiring the resolver directly to the lambda.

The Lambda

Finally we need to create a lambda that the AppSync Function invokes when called. In this case, we’re using the Python 3.8 runtime, but the beauty of using a lambda in this context is you can use whatever language and development frameworks you are most comfortable using.

If you reference GitHub, you’ll find some details on how we deployed this Lambda. We’ll skip over these details here because they are beyond the scope of this article.

Assuming we’ve deployed this Lambda, let’s look at how we define a Lambda Appsync Datasource:

The key here is the lambda_config which specifies which function we want to invoke when we connect a function or resolver to this data source. This resource is the data_source for both our function and resolver above. We are now all set to test out our work and returning to the “Where are we going?” section above will detail such.

Summary

We’ve created an AppSync GraphQL API and connected it to a Lambda using Terraform. We encourage you to get your hands dirty be trying this out yourself with the supplementary GitHub Repository. Please raise an issue there if you run into trouble and we’ll do what we can to help you out. Of course, there is no replacement for the official documentation and encourage you to head there next.

FAQs

Why not use AppSync resolver mapping template language (VTL) instead of adding Lambdas to the mix?

If the logic in your use case is straightforward or nonexistent, use VTL. It’s going to be faster. If your needs are such that you need to implement more complex logic, then the developer experience that you are comfortable with in your preferred Lambda runtime is going to be your friend.

Often if we think we should write a test for a function, then we use Lambda and have found this saves us a fair bit of frustration later on. Of course, there are going to be things that VTL simply won’t suffice for like integrating with a 3rd party API.

Here is what the same logic in our example Lambda would look like in VTL. It could be placed directly in either the AppSync Function or AppSync Resolver mapping templates to achieve the same result as we have with the Lambda:

Additional Reading

--

--