DEV Community

Tim Veletta for AWS Community Builders

Posted on • Originally published at timveletta.com on

Direct Lambda resolvers with AWS Amplify

AWS Amplify is fantastic; it has never been easier to build and provision a cloud-based, full-stack, web application. One of the best features of Amplify is the ability to quickly generate and build a GraphQL API using AWS AppSync and have it seamlessly work with your front-end using generated queries and mutations based off your schema.

By default, when creating an AppSync API through Amplify, Velocity templates are used as resolvers to perform simple CRUD operations on your underlying database. These resolver templates are largely a black box to the developer and don't do much out of the ordinary so when it comes to performing more than just a simple CRUD operation, we need to look elsewhere.

Thankfully, we also have the option to easily provision and manage Lambda resolvers through AWS Amplify to do the job for us. I'm going to show you the easiest way to achieve this.

Provisioning the Resources

After creating a simple GraphQL API through Amplify, we have a simple Todo model in my schema.graphql with the following structure.

type Todo @model {
  id: ID!
  name: String!
  description: String
}
Enter fullscreen mode Exit fullscreen mode

If, for instance, we wanted to perform an additional operation whenever we added a Todo such as sending an email to a user or interacting with another AWS service; the provided AppSync resolver wouldn't be enough. This is where Lambda resolvers come in and the convenience of Amplify allows us to easily provision them using amplify add function as shown below.

% amplify add function
? Select which capability you want to add: Lambda function (serverless function)
? Provide a friendly name for your resource to be used as a label for this category in the project: addTodo
? Provide the AWS Lambda function name: addTodo
? Choose the 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 in this project from your Lambda function? Yes
? Select the category storage
Storage category has a resource called Todo:@model(appsync)
? Select the operations you want to permit for Todo:@model(appsync) create

You can access the following resource attributes as environment variables from your Lambda function
    API_DIRECTRESOLVER_GRAPHQLAPIIDOUTPUT
    API_DIRECTRESOLVER_TODOTABLE_ARN
    API_DIRECTRESOLVER_TODOTABLE_NAME
    ENV
    REGION
? Do you want to invoke this function on a recurring schedule? No
? Do you want to configure Lambda layers for this function? No
? Do you want to edit the local lambda function now? No
Successfully added resource addTodo locally.
Enter fullscreen mode Exit fullscreen mode

In the above example, we have provisioned a Lambda function and given it access to create data in our DynamoDB table since we want to record data there. From here we can perform an amplify push to provision the infrastructure on AWS.

Making the Schema changes

At this point, the Lambda function is provisioned and available but we can't actually access it through our AppSync API. To do this, we simply create a new mutation and input type within our schema.graphql and apply the @function directive to the mutation that will call the Lambda function.

input AddTodoInput {
  name: String!
  description: String
}

type Mutation {
  addTodo(input: AddTodoInput!): Todo @function(name: "addTodo-${env}")
}
Enter fullscreen mode Exit fullscreen mode

I've just included some dummy logic within my Lambda function which just returns an object when it is called however you probably want to insert the record into the DynamoDB table and include some other logic. You can find out how to insert data into DynamoDB using this page.

exports.handler = async (event) => {
  // TODO include any additional logic here
  return {
    id: "1234",
    name: "Finish blog post",
    description: "AWS Amplify is great",
  };
};
Enter fullscreen mode Exit fullscreen mode

Once again, we have to run amplify push to update our changes; then we can go into the AppSync console and run our mutation to see the results below.

Calling our mutation through the AppSync console.

So that's it, easy enough right!

The resulting Resolver

Well, its not technically a direct Lambda resolver as the title says. If we look at our AppSync schema, we can see that Amplify has actually created a Pipeline resolver for us.

A Pipeline resolver allows us to compose multiple functions into a single call to the GraphQL API. It does so by calling a Velocity template prior to the function or functions, mapping the data from the GraphQL inputs to a Lambda event. Another Velocity template is ran after each of the functions, mapping the data from the last function to the GraphQL response.

Our resulting pipeline resolver.

In my opinion, this isn't necessarily a bad thing. You might be sacrificing a miniscule amount of execution speed since calling the resolver now involves running a Velocity template both before and after your Lambda function execution, however, it now allows you to do some great things such as chaining function executions to include additional logic.

For example, if we wanted to include logic to send a message after both adding and removing a Todo, we could create the function to send a message once, and reuse it across multiple resolvers.

type Mutation {
  addTodo(input: AddTodoInput!): Todo
    @function(name: "addTodo-${env}")
    @function(name: "sendMessage-${env}")
  removeTodo(id: ID!): Todo 
    @function(name: "removeTodo-${env}") 
    @function(name: "sendMessage-${env}")
}
Enter fullscreen mode Exit fullscreen mode

Pushing our changes using amplify push and then viewing the AppSync console, we can see the two functions that are chained together to form our pipeline resolver.

Our pipeline resolver with multiple function calls.

AWS Amplify has not only made it super easy to create and provision a new AppSync API, but if the out-of-the-box resolvers aren't doing everything you need, it is just as easy to use a Lambda resolver instead as we saw above.

Discussion (4)

Collapse
nxtra profile image
Nxtra

Nice article, to the point.
If you really want to you can remove the mapping templates too. It is described here: dev.to/aws-builders/direct-lambda-...

Collapse
andthensumm profile image
Matt Marks 🐣 • Edited on

What makes a Lambda a Direct Resolver is when the Request and Response mapping requests are removed from the AppSync Resolver for the Lambda, not the Before and After mapping templates in the pipeline resolvers generated from the @function directive.

As shown in the image, you still have request/response mapping templates for both your lambdas. You'd need to remove those Request/Response templates and then you'd have a Direct Lambda Resolver inside a Pipeline Resolver.

Collapse
timmahh profile image
Tim Veletta Author

I was probably a bit misleading regarding the title but I feel like the additional work to remove the mapping templates to make it a true Direct Resolver is somewhat unnecessary.

From a developers point of view, they don't have to worry about touching the mapping templates which I think is the real benefit of using Amplify so in essence it feels like it is direct.

Having said that, in my personal opinion, the fact that it sets up a pipeline resolver for you out of the box is a fantastic decision since you start out thinking about how you're going to compose your functions to reuse as much logic.

Thread Thread
andthensumm profile image
Matt Marks 🐣

I agree that not touching CFT is a benefit of Amplify but as mentioned in the release, the intent of Direct Lambda Resolvers is no VTL to deal with.

I think this article is great for showing how to use the @function directive though