This is a series of articles about setting up a complex Serverless backend infrastructure with AWS SAM and CloudFormation.
Here is the index of all the articles in case you want to jump to any of them:
1. Setup of AppSync and API Gateway with Multiple AWS Cognito User Pools
2. Configuring S3 Buckets with Permissions and Access Roles in AWS Cognito AuthRole
3. Intro to DynamoDB Resolvers for AppSync Implementation
- Intro to Lambda Resolvers for AppSync Implementation
5. Configuring an AWS VPC to Include Lambda Resolvers with a Fixed IP
6. Intro to Pipeline Resolvers for AppSync Implementation
7. Handling Lambda Resolver Timeouts with SNS Messages
DynamoDB resolvers are fast to set up, but they hit a wall the moment you need business logic. Validate input before writing? Aggregate data from multiple sources? Call an external API? You need compute. That's where Lambda resolvers come in.
A Lambda resolver lets you run any code you want between the GraphQL operation and the data source. AppSync invokes your function, your function does whatever it needs, and returns the result. Full control.
What Lambda Resolvers Give You
For each CRUD operation, a Lambda function handles the logic:
- Create: Validate input, enforce business rules, integrate with other services, then store the data.
- Read: Aggregate from multiple sources, apply complex filtering, transform the response.
- Update: Run validation, check permissions, apply business logic before updating.
- Delete: Archive data, notify downstream services, clean up related resources before removing.
The CloudFormation Setup
Here's how to configure a Lambda function as an AppSync data source — the function itself, its execution role, and the AppSync integration:
Resources:
# Lambda Function
MyLambdaFunction:
Type: "AWS::Lambda::Function"
Properties:
Handler: "index.handler"
Role: !GetAtt LambdaExecutionRole.Arn
FunctionName: "MyAppSyncLambdaDataSource"
Code:
S3Bucket: "myBucket"
S3Key: "code/myLambda.zip"
Runtime: "nodejs14.x"
MemorySize: 256
Timeout: 10
# IAM Role for Lambda Execution
LambdaExecutionRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service: "lambda.amazonaws.com"
Action: "sts:AssumeRole"
Policies:
- PolicyName: "LambdaExecutionPolicy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
Resource: "arn:aws:logs:*:*:*"
# AppSync Lambda DataSource
AppSyncLambdaDataSource:
Type: "AWS::AppSync::DataSource"
Properties:
ApiId: !GetAtt AppSyncAPI.ApiId
Name: "LambdaDataSource"
Type: "AWS_LAMBDA"
LambdaConfig:
LambdaFunctionArn: !GetAtt MyLambdaFunction.Arn
ServiceRoleArn: !GetAtt AppSyncLambdaRole.Arn
# IAM Role for AppSync to call Lambda
AppSyncLambdaRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service: "appsync.amazonaws.com"
Action: "sts:AssumeRole"
Policies:
- PolicyName: "AppSyncLambdaPolicy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "lambda:InvokeFunction"
Resource: !GetAtt MyLambdaFunction.Arn
Three resources beyond the Lambda itself: an execution role, a data source configuration, and an IAM role that lets AppSync invoke the function. That's the bridge between your GraphQL schema and your custom code.
Use DynamoDB resolvers when all you need is a pass-through. Reach for Lambda the moment you need to think before you write.
Code examples are simplified to illustrate the approach. Some adjustments may be needed for production.
Next UP: Part 5. Configuring an AWS VPC to Include Lambda Resolvers with a Fixed IP
