How To Use AWS AppSync in Lambda Functions
In this tutorial, you are going to learn how you can execute queries and mutations in a Lambda function on an AppSync GraphQL API. We are going to use the Amplify framework to generate our code, but the solutions here work whether you use Amplify, or Serverless or something else.
Note: This article was first posted on my blog. I publish each article two weeks later on Medium. You might want to subscribe to my newsletter because you will get content like this earlier! 💌
Do you even have a backend if you can only modify your database from your client side? There might be operations that you want to prohibit your users from doing. A malicious user might be able to abuse your client-side code. The fix is to have your server manipulate your database, too 👏🏻.
After I read Nader’ Dabit’s excellent tutorial “Lambda Function GraphQL Resolvers”, I wanted to know how I could modify my existing DynamoDB backend from a Lambda function. (Nader only shows how to generate a new database.) When I finally found a solution, I found out that modifying your DynamoDB directly comes with a significant disadvantage: If you have subscriptions or pipeline resolvers set up for your AppSync API they are ignored, and you will NOT trigger them. I needed to find a way to use AppSync from Lambda.
This guide covers how you can access your AppSync API from a Lambda function. We are going to use Amplify to generate the resources, and we are going to use the AppSync client and console to execute the queries and tweak our schemas. I’ll leave your client-side code up to you, and we’ll focus on the Amplify, AppSync and Lambda code.
Here is an overview of the steps.
- Create a Lambda function, which uses the AppSync client to perform GraphQL operations. Use polyfills and install all necessary dependencies.
- Ensure the Lambda function has the right execution policy.
- Use AppSync’s multi auth to allow both requests that are signed by Amazon Cognito User Pools as well as requests that are signed using Amazon’s IAM. This way, both the client and the server (aka. the Lambda function) will be authenticated and can have different CRUD permissions.
Note: You want to avoid hardcoding user credentials in your Lambda function. It is best practice for a Lambda function to use an IAM policy on its execution role to interact with a destination service, which is why we take the approach described above.
Let’s do it 🚀. Start by initializing your Amplify project.
amplify init
? Enter a name for the project
appsynclambda
? Enter a name for the environment
master
? Choose your default editor:
<your-editor>
? Choose the type of app that you\'re building
javascript
Please tell us about your project
? What javascript framework are you using
react
? Source Directory Path:
src/
? Distribution Directory Path:
build/
? Build Command:
npm run-script build
? Start Command:
npm run-script start
? Do you want to use an AWS profile?
Yes
? Please choose the profile you want to use
<your-profile>
The first category we are going to add is authentication.
amplify add auth
Do you want to use the default authentication and security configuration?
Default configuration
How do you want users to be able to sign in when using your Cognito User Pool?
Email
What attributes are required for signing up? (Press <space> to select, <a> to toggle all, <i> to invert selection)
Email
Secondly, we need to add the GraphQL endpoint. This will also generate a DynamoDB for you.
amplify add api
? Please select from one of the below mentioned services
GraphQL
? Provide API name:
appsyncTodo
? Choose an authorization type for the API
Amazon Cognito User Pool
Use a Cognito user pool configured as a part of this project
? Do you have an annotated GraphQL schema?
No
? Do you want a guided schema creation?
Yes
? What best describes your project:
Single object with fields (e.g., “Todo” with ID, name, description)
? Do you want to edit the schema now?
Yes
And here is how you want to edit that schema.
We create a model for the todos, which will create a DynamoDB table. The owner of the todos has full CRUD permissions for the todos. We’ll later modify this in the Amplify console. Keep in mind: whenever you push changes from Amplify, it will overwrite the work that you did in the console. Remember to add the necessary directives after each push. If this doesn’t make sense to you, keep reading. It will make sense soon. I just wanted to get this in your head as early as possible.
Commit your changes to the cloud.
amplify push
? Are you sure you want to continue?
Yes
? Do you want to generate code for your newly created GraphQL API
Yes
? Choose the code generation language target
javascript
? Enter the file name pattern of graphql queries, mutations and subscriptions
src/graphql/**/*.js
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions
Yes
? Enter maximum statement depth [increase from default if your schema is deeply nested]
2
Now, we want to add the Lambda function.
amplify add function
Using service: Lambda, provided by: awscloudformation
? Provide a friendly name for your resource to be used as a label for this category in the project:
lambdaTodo
? Provide the AWS Lambda function name:
lambdaTodo
? Choose the function template that you want to use:
Serverless express function (Integration with Amazon API Gateway)
? Do you want to access other resources created in this project from your Lambda function?
Yes
? Select the category
api
? (<Only prompted if>) Api has 2 resources in this project. Select the one you would like your Lambda to access
appsyncTodo
? Select the operations you want to permit for todostreak (Press <space> to select, <a> to toggle all, <i> to invert selection)
create, read, update, delete
? Do you want to edit the local lambda function now?
Yes
(If you already have a Lambda function update it using amplify update function
because the latest version of the CLI will auto-generate the necessary environment variables and policies.) Modify your Lambda function to reflect the following.
What we are doing here is first polyfilling fetch
so that we can use it. ( Lambda functions run in a Node 8.10 environment.) Consequently, we configure the aws-sdk
and the AWSAppSyncClient
. Then we execute the query.
Make sure your Lambda function has the correct permissions.
If your function has the correct permission, it will automatically have access to the necessary environment variables such as process.env.AWS_ACCESS_KEY_ID
, process.env.AWS_SECRET_ACCESS_KEY
and process.env.AWS_SESSION_TOKEN
.
You will also need to add some dependencies to its package.json
.
Or use npm
.
npm install --save apollo-cache-inmemory apollo-client apollo-link apollo-link-http aws-appsync aws-sdk aws-sdk es6-promise graphql graphql-tag isomorphic-fetch
Remember to run npm install
from the function's src
directory if you want to test it locally. Furthermore, you need to create a file at src/graphql.js
which contains the query.
Lastly, add the function to a REST
API.
amplify add api
? Please select from one of the below mentioned services
REST
? Provide a friendly name for your resource to be used as a label for this category in the project:
lambdaTodo
? Provide a path (e.g., /items)
/items
? Choose a Lambda source
Use a Lambda function already added in the current Amplify project
? Choose the Lambda function to invoke by this path
lambdaTodo
? Restrict API access
Yes
? Who should have access?
Authenticated users only
? What kind of access do you want for Authenticated users?
create
? Do you want to add another path?
No
Push your changes. Subsequently, visit your AppSync console, choose your API and click on “Schema”. Scroll down until you see your Query
or Mutation
. I want to perform the listTodos
query in Lambda, so I grant access to it using both @aws_iam
and @aws_cognito_user_pools
.
You can use the auth directive’s on a per field basis like demonstrated above or you can do it on each type to give all its attributes the directives ( type Query @aws_iam @aws_cognito_user_pools {
). ModelTodoConnection
will also need these permissions because listTodos
returns it.
type ModelTodoConnection @aws_iam @aws_cognito_user_pools {
items: [Todo]
nextToken: String
}
Remember: what you modify in the console will be overwritten every time you push changes to this API
Hit save, and you are done 🔥. You should now be able to access the function from your client. Here is how you do that using Amplify in JavaScript.
If you set up your schema the same way I did and created some todos, notice how the items
array that you get back is empty. That's because the todos are filtered in AppSync's .vtl
template. If you don't know what .vtl
templates are and how AppSync uses them as resolver templates for your GraphQL requests, you might want to read "Query More Items Using Scans in AWS Amplify" and "Creating GraphQL Batch Operations for AWS Amplify". In these two tutorials, I explain what .vtl
templates are and you are going to learn how you can write your own. Then you should be able to write a custom query and its resolver so that you get some items back.
Summary
We polyfilled some functions which the AppSync client needs and configured it alongside the aws-sdk
to perform GraphQL operations from our Lambda function. We ensured the Lambda function has the necessary permissions by tweaking our GraphQL schema.
If you are using Amplify, take a look at this PR. It will bring AppSync’s capability to support multiple authorization types to GraphQL transform. If it’s already merged check out my other blog posts because I’ll probably have written a guide on how to use that directive.
Note: This article was first posted on my blog. I publish each article two weeks later on Medium. You might want to subscribe to my newsletter because you will get content like this earlier! 💌
Originally published at https://geromekevin.com.