Lambda Resolvers with AppSync

Tags: appsync dynamodb cognito lambda s3 python vuejs velocity vtl

We explore using AppSync with Lambda resolvers to interact with DynamoDB, S3, and Cognito.

Goals:

  • Define a GraphQL query that retrieves a record
  • Understand the VTL resolver autogenerated by AppSync to handle that query
  • Define a Lambda resolver to augment/replace the generated VTL
  • Expand the Lambda resolver to perform a task that would otherwise be difficult or impossible with VTL, integrating the use of Python libraries or other AWS services

Questions

Can you use a Lambda resolver to retrieve an item from DynamoDB?

Yes! An AWS Lambda function can be used to handle any query or mutation. Rather than replacing the VTL code, though, it works in conjunction with it:

  1. A GraphQL query is received by AppSync. For example, this can be from the AppSync GraphQL query console, or from a Vue component containing the query operation.

  2. AppSync sends it to the query's corresponding request mapping template:

{
  "version": "2017-02-28",
  "operation": "Invoke",
  "payload": {
    ## Imagine this is a resolver for a getTask GraphQL query
    "query": "getTask",
    ## We pass in the entire context so we have all the arguments, identity,
    ## and other request data in our Lambda function.
    "context": $utils.toJson($context)
  }
}
1
2
3
4
5
6
7
8
9
10
11

The above is a Velocity (.vtl) template, where $utils is a library of utility functions provided by AppSync, and $context is the data about the incoming query, including the arguments and Cognito identity data for the requesting user.

  1. The request mapping template sends an Invoke operation which executes the Lambda function. Here we use Python, but you can use JS or Go if you prefer.
def main(event, aws_context):
    """
    If using the request mapping template above, the `event` argument contains
      these two items:
       :query: What was passed in as the query value from the request mapping
               template in the previous example.
       :context: All the context from GraphQL is in the context.
    """
    # ... your processing code here
    return some_item
1
2
3
4
5
6
7
8
9
10
  1. The returned value from the Lambda function is sent to the query's response mapping template, also in VTL:
$util.toJson($context.result)
1

If desired, you can put additional logic in the response mapping template. But you might as well put it into the Lambda function if possible, since VTL is tricky to work with due to its sparse documentation.

  1. The results of your GraphQL query are sent back!

What processing is needed to transform the DynamoDB item into a format the GraphQL query can return and use seamlessly with AppSync?

We found that just called get_item isn't enough, you have to flatten the response to a more standard JSON response that GraphQL can parse to match the requested fields.

Original DynamoDB response:

{
  "id": {"S": "ACDEF-DEFGHI-JKLMN-OPQRST"},
  "timestamp": {"N": 1388184300},
  "title": {"S": "Audrey + Daniel"},
  "married": {"B": true}
}
1
2
3
4
5
6

After flattening:

{
  "id": "ACDEF-DEFGHI-JKLMN-OPQRST",
  "timestamp": 1388184300,
  "title": "Audrey + Daniel",
  "married": true
}
1
2
3
4
5
6

The library that we used to flatten the response is dynamodb-json.

Can data be constrained to the owner or particular Cognito users?

Absolutely! For example, if the item retrieved from DynamoDB says its owner is audreyr, you can check that the query was made by a user named audreyr:

if not item['owner'] == username:
    return {"message": "Forbidden"}
return item
1
2
3

What we like to do then is in the VTL handle the response thus:

#if($context.result["message"] == "Forbidden")
  $utils.unauthorized()
#else
  $util.toJson($context.result)
#end
1
2
3
4
5

Can you generate S3 presigned urls based on the permissions of the requesting Cognito user and return them as part of the query?

Yes, it's just a matter of importing boto3, instantiating an s3 client, and calling the client's get_presigned_url method. Don't forget to set the Lambda's role appropriately.

Can other AWS services be integrated in a way that wouldn't otherwise be possible with VTL?

Yes, anything that can be called by boto3 or another AWS client library that runs on lambda can be integrated into your resolver function.

Can Lambda resolvers be defined such that they are completely specified in CloudFormation/Serverless in a way that can be saved in version control, including all:

  • Lambda Python code
  • Supporting VTL resolvers
  • DynamoDB tables
  • IAM roles needed to create/update/manage all of the above components

Yes, a simple working example of how to do this can be found in the serverless-appsync-plugin repo.


Copyright © 2018 Daniel and Audrey Roy Greenfeld.
Site Map

Last Updated: 8/31/2018, 11:57:19 PM