Direct Lambda Resolvers with AWS Amplify and AppSync
Tags: aws, serverless, node, amplify
Published: 2020-08-12 22:43:40

## How I feel about VTL on any given day ##
![woman raising fist saying "curse youuuu!"](https://media.giphy.com/media/MwtjauC5lamQM/giphy.gif)
 >Haha okay, okay. I'm just kiddin' (mostly).

AWS recently announced [Direct Lambda Resolvers](https://aws.amazon.com/blogs/mobile/appsync-direct-lambda/) support, which means you can write AppSync resolvers in your favorite Lambda runtimes without relying on the Velocity Template Language. 

I wanted to take advantage of this, as I had some complicated logic that that I didn't know how to write with VTL and it was much easier to do with javascript.  I'll share with you how I added Direct Lambda Resolvers to an Amplify project with a little magic from the @function directive. 

*I'm gonna assume you've already created a GraphQL API for 
your Amplify project.  If you haven't, follow the steps here on how to [Create a GraphQL API](https://docs.amplify.aws/lib/graphqlapi/getting-started/q/platform/js)*


### Step 1:  Create a Lambda with Amplify cli
```bash
amplify add function
? Provide a friendly name for your resource to be used as a label for this category in the project: DirectLambdaResolver
? Provide the AWS Lambda function name: DirectLambdaResolver
? Choose the function runtime that you want to use: NodeJS
? Choose the function template that you want to use: Hello World
? Do you want to access other resources created in this project from your Lambda function? No
? Do you want to invoke this function on a recurring schedule? No
? Do you want to edit the local lambda function now? Yes
```
Go to `amplify/backend/function/DirectLambdaResolver/src/index.js` and update it with the following: 
```javascript
let response;

exports.handler = async (event, context) => {
    console.log({event, context})
    try {
        response = "this lambda is super direct"
    } catch (err) {
        console.log({err})
        console.log(err);
        return err;
    }

    return response
};
```
Alright, save the file and press enter in the terminal to continue.

### Step 2: Auto-generate code with `@function` and copy code to CustomResources.json
Now we're gonna go to `amplify/backend/api/[your-api-name]/schema.graphql` and take advantage of the `@function` directive

```javascript
### schema.graphql
type Todo @model {
  id: ID
  task: String @function(name: "DirectLambdaResolver-${env}")
}
```
then
```bash
$: amplify api gql-compile
```

This will create all the files inside `amplify/backend/api/[your-api-name]/build/`.  What we're looking for is the file `FunctionDirectiveStack.json`.  Open it up and you'll see the following: 
```json
{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Description": "An auto-generated nested stack.",
    "Metadata": {},
    "Parameters": {
      ...
    },
    "Resources": {
      // IAM Role
      "DirectLambdaResolverLambdaDataSourceRole": {
         ...
      },
      // DataSource
      "DirectLambdaResolverLambdaDataSource": {
         ...
      },
      // Direct Lambda Resolver
      "InvokeDirectLambdaResolverLambdaDataSource": {
         ...
      },
      // Pipeline Resolver
      "TodotaskResolver": {
         ...
      }
    }
``` 
>When amplify auto-generates with @function, it creates a Lambda DataSource, an IAM Role for that DataSource, the Direct Lambda Resolver and a Pipeline Resolver.

We're going to copy everything from the `Resources` object and paste it in our `CustomResources.json` file located at `amplify/backend/api/[your-api-name]/stacks/`.  It should now look like the following:
```json
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "An auto-generated nested stack.",
  "Metadata": {},
  "Parameters": {
     ...
  },
  "Resources": 
    "DirectLambdaResolverLambdaDataSourceRole": {
      ...
    },
    "DirectLambdaResolverLambdaDataSource": {
      ...
    },
    "InvokeDirectLambdaResolverLambdaDataSource": {
      ...
    },
    "TodotaskResolver": {
      ...
    }
  },
  "Conditions": {
    ...
  },
  "Outputs": {
    ...
  }
}
```
We also need to update our `DirectLambdaResolverLambdaDataSourceRole` to replace `GetAttGraphQLAPIApiId` with `AppSyncApiId`
```json
"DirectLambdaResolverLambdaDataSourceRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "RoleName": {
          "Fn::If": [
            "HasEnvironmentParameter",
            {
              "Fn::Join": [
                "-",
                [
                  "DirectLambdaResolver774c",
                  {
                    "Ref": "AppSyncApiId" // Used to be GetAttGraphQLAPIApiId
                  },
                  {
                    "Ref": "env"
                  }
                ]
              ]
            },
            {
              "Fn::Join": [
                "-",
                [
                  "DirectLambdaResolver774c",
                  {
                     "Ref": "AppSyncApiId" // Used to be GetAttGraphQLAPIApiId
                  }
                ]
              ]
            }
          ]
        },
        "AssumeRolePolicyDocument": {
         ...
        },
        "Policies": [
          ...
        ]
      }
    },
```
 
Next up, in order to make our new Lambda a Direct Lambda Resolver with AppSync, we'll need to remove the request and response mapping templates from our function `InvokeDirectLambdaResolverLambdaDataSource`. They're nested inside the `Properties` object
```json
"InvokeDirectLambdaResolverLambdaDataSource": {
  "Type": "AWS::AppSync::FunctionConfiguration",
  "Properties": {
    "ApiId": {
        "Ref": "AppSyncApiId"
    },
    "Name": "InvokeDirectLambdaResolverLambdaDataSource",
    "DataSourceName": "DirectLambdaResolverLambdaDataSource",
    "FunctionVersion": "2018-05-29",
    // REMOVE ME 
    "RequestMappingTemplateS3Location": {
      ...
    },
    // REMOVE ME 
    "ResponseMappingTemplateS3Location": {
      ...
    }
  },

  "DependsOn": "DirectLambdaResolverLambdaDataSource"
}
```
We'll also need to copy `Todo.task.req.vtl` and `Todo.task.res.vtl` from `amplify/backend/api/[your-api-name]/build/resolvers` to `amplify/backend/api/[your-api-name]/resolvers`.  
>**Wait a minute.  I thought we said no more VTL?  What gives?**
`TodotaskResolver` is a Pipeline Resolver. `InvokeDirectLambdaResolverLambdaDataSource` is our Direct Lambda Resolver.  
Don't wan't a Pipeline resolver?  Don't worry, I got you covered towards the end :wink:

### Step 3:  Remove autogenerated code
Remember how we added `@function(name: "DirectLambdaResolver-${env}` directive in our `task: String` field within our  schema.graphql?  We need to remove all the generated code, including `FunctionDirectiveStack.json`.  

Luckily that's pretty easy.  Just remove the `@function` and run: 
```bash
$: amplify api gql-compile
```
If you look inside `amplify/backend/api/[your-api-name]/build/`, you'll see our updated CustomResources.json file and that the FunctionDirectiveStack.json has been removed.  

Puh-push it real good
```bash
$: amplify push
```
If you go to your AppSync console, click on schema and search `Todo` inside the Resolvers search field, and scroll down to `task` and it should say `Pipeline`.  Click on it and it will take you to the Todo.task Pipeline Resolver with your DirectLambdaResolver listed.  Notice the mapping templates disabled?  

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/cdumdvgh8ss0uu4ictjj.png)

Yeaaa buddy, no more VTL (kind of)  :satisfied: :satisfied: :satisfied:

### Bonus Round: Remove Pipeline Resolver
As I promised earlier, if you want to get rid of the Todo.task Pipeline Resolver, we need to make two changes inside  `amplify/backend/api/[your-api-name]/stacks/CustomResources.json`.

First, delete `TodotaskResolver`.
Second, replace your `InvokeDirectLambdaResolverLambdaDataSource` with the following: 
```json
"TodotaskDirectLambdaResolver": {
  "Type": "AWS::AppSync::Resolver",
  "Properties": {
    "ApiId": {
        "Ref": "AppSyncApiId"
    },
    "DataSourceName": "DirectLambdaResolverLambdaDataSource",
    "TypeName": "Todo",
    "FieldName": "task"
  },

  "DependsOn": "DirectLambdaResolverLambdaDataSource"
}
``` 

```bash
$: amplify api push
```

**Now, go forth and banish VTL templates at will!**

I hope you enjoyed this post and if you did, follow me on twitter for all things AWS Amplify, AppSync, and React-Native :raised_hands: :raised_hands: :raised_hands: 
https://twitter.com/andthensumm

Resources:
[Add a custom resolver that targets an aws lambda](https://docs.amplify.aws/cli/graphql-transformer/resolvers#add-a-custom-resolver-that-targets-an-aws-lambda-function)
[How to add pipelineResolvers - Github Issue](https://github.com/aws-amplify/amplify-cli/issues/3321)