Prerequisites:
- AWS CLI (See: AWS CLI installation).
GitHub Repository:
Languages & Frameworks:
- Each of these CloudFormation examples is written in YAML.
- We also use Node.js for our function code, but the examples can be adapted to any AWS Lambda-supported language, such as Python, Ruby, Java, C#, Go and even PHP.
Using CloudFormation to Deploy to Lambda
A simple "Hello World" CloudFormation Lambda example:
minimal-example.yml:
Resources:
LambdaFunction:
Type: 'AWS::Lambda::Function'
Properties:
FunctionName: AwsLambdaMinimalExample
Handler: index.handler
Runtime: nodejs14.x
Role: !GetAtt LambdaFunctionRole.Arn
MemorySize: 1024
Code:
ZipFile: |
exports.handler = async (event) => {
return "Hello World!";
}
LambdaFunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
Policies:
- PolicyName: AppendToLogsPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
To deploy the stack:
aws cloudformation create-stack \
--stack-name minimal-example \
--capabilities CAPABILITY_NAMED_IAM \
--template-body file://minimal-example.yml
To monitor the deployment:
aws cloudformation describe-stack-events \
--stack-name minimal-example
To invoke the function:
aws lambda invoke \
--function-name AwsLambdaMinimalExample \
function-result.json
To print the function's output:
cat function-result.json
This should read:
"Hello World"
Note:
- All AWS Lambda functions return JSON.
- As such, string responses will be wrapped in double quotes.
CloudFormation Lambda Function Code: Inline vs ZIP File
To deploy a Lambda function with inline code:
Code:
ZipFile: |
exports.handler = async (event) => {
console.log("Hello World!")
}
To deploy a Lambda function from a bundled/zipped codebase is a little harder, as it requires S3:
Code:
S3Bucket: my-lambda-function-code
S3Key: MyBundledLambdaFunctionCode.zip
To bundle your code – and to use AWS CloudFormation to deploy the ZIP file to Lambda – do the following:
- ZIP your codebase. (The ZIP file must contain an
index.js
at the root, with yourhandler
function as a named export.) - Upload the ZIP file to S3.
- Reference the ZIP file from your CloudFormation template, like in the example above.
Note: Handler: index.handler
in the CloudFormation template means "use index.js
as the entry point and handler
as the function".
How to Invoke your Lambda Function
There are 3 ways to invoke your function:
- Using the AWS SDK (and by extension: the AWS CLI).
- Using AWS Lambda Triggers (to respond to SQS events, S3 events, etc.).
- Using API Gateway (to respond to HTTP requests).
Here's some examples to demonstrate each option:
Quick Recap: Invoke using the AWS CLI
To invoke your function via the AWS CLI:
aws lambda invoke \
--function-name AwsLambdaMinimalExample \
function-result.json
Quick Recap: Invoke using the AWS SDK
To invoke your function programmatically, first install the AWS SDK:
npm install @aws-sdk/client-lambda@3
To invoke using the AWS SDK:
import { Lambda } from "@aws-sdk/client-lambda";
const functionName = "AwsLambdaMinimalExample" // CHANGE ME
const lambda = new Lambda({});
async function executeLambda() {
const result = await lambda.invoke({ FunctionName: functionName });
if (result.FunctionError !== undefined) {
throw new Error(result.FunctionError);
}
if (result.Payload !== undefined) {
const jsonString = Buffer.from(result.Payload).toString('utf8')
return JSON.parse(jsonString)
}
return undefined
}
AWS CloudFormation API Gateway Examples
To invoke your function over HTTP, you'll need use API Gateway.
Before you continue, you'll need to decide which integration type to use:
- HTTP API is the new lightweight option. If you prefer to handle HTTP request routing yourself (i.e. in your function's code), then this option will serve you best.
- REST API is the older, more feature-rich option. You'll need to define each individual route/endpoint in your CloudFormation YAML, but you'll benefit from extra features like rate limiting, usage plans, and so forth.
API Gateway Lambda CloudFormation (HTTP API)
To expose your function as an HTTP API:
http-api-example.yml:
Resources:
HandlerLambdaFunction:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: |
exports.handler = async (event) => {
return {
isBase64Encoded: false,
statusCode: 200,
headers: { "content-type": "text/plain"},
body: "Hello World!"
}
}
Handler: index.handler
Role: !GetAtt LambdaFunctionRole.Arn
Runtime: nodejs14.x
# Creates a single-route (wildcard) HTTP API for our Lambda function.
HttpApi:
Type: AWS::ApiGatewayV2::Api
Properties:
Name: my-example-api
ProtocolType: HTTP
HttpApiIntegration:
Type: AWS::ApiGatewayV2::Integration
Properties:
ApiId: !Ref HttpApi
IntegrationType: AWS_PROXY
IntegrationUri: !GetAtt HandlerLambdaFunction.Arn
PayloadFormatVersion: '2.0'
HttpApiRoute:
Type: AWS::ApiGatewayV2::Route
DependsOn: HttpApiIntegration
Properties:
RouteKey: $default
ApiId: !Ref HttpApi
Target:
!Join
- /
- - integrations
- !Ref HttpApiIntegration
HttpApiStage:
Type: AWS::ApiGatewayV2::Stage
Properties:
StageName: $default
ApiId: !Ref HttpApi
AutoDeploy: true
# Allows the Lambda function to access CloudWatch (add other services here, e.g. S3)
LambdaFunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
Policies:
- PolicyName: AppendToLogsPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
# Allows API Gateway to call our Lambda function.
HandlerLambdaPermissionHttpApi:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !GetAtt HandlerLambdaFunction.Arn
Principal: apigateway.amazonaws.com
SourceArn:
!Join
- ''
- - 'arn:'
- !Ref AWS::Partition
- ':execute-api:'
- !Ref AWS::Region
- ':'
- !Ref AWS::AccountId
- ':'
- !Ref HttpApi
- /*
# Outputs the URL to our Lambda HTTP API.
Outputs:
ApiUrl:
Description: URL of the HTTP API.
Value:
!Join
- ''
- - !Ref HttpApi
- .execute-api.
- !Ref AWS::Region
- .
- !Ref AWS::URLSuffix
To deploy the HTTP API stack:
aws cloudformation create-stack \
--stack-name http-api-example \
--template-body file://http-api-example.yml \
--capabilities CAPABILITY_NAMED_IAM
To monitor the HTTP API's deployment:
aws cloudformation describe-stack-events \
--stack-name http-api-example
After it's deployed, get your Lambda's public URL with:
aws cloudformation describe-stacks \
--stack-name http-api-example \
--query "Stacks[0].Outputs[?OutputKey=='ApiUrl'].OutputValue" \
--output text
API Gateway Lambda CloudFormation (REST API)
To expose your function as a REST API (i.e. using API Gateway V1):
rest-api-example.yml:
Resources:
# Our API: we'll use a separate Lambda function per route.
RestApi:
Type: AWS::ApiGateway::RestApi
Properties:
EndpointConfiguration:
Types:
- EDGE
Name: simple-rest-endpoint
RestApiDeployment:
Type: AWS::ApiGateway::Deployment
DependsOn:
- HelloWorldApiGatewayMethod
Properties:
RestApiId: !Ref RestApi
StageName: dev
# Route 1: GET /hello_world
HelloWorldApiGatewayResource:
Type: AWS::ApiGateway::Resource
Properties:
ParentId: !GetAtt RestApi.RootResourceId
PathPart: hello_world
RestApiId: !Ref RestApi
HelloWorldApiGatewayMethod:
Type: AWS::ApiGateway::Method
DependsOn:
- HelloWorldFunctionApiGatewayPermission
Properties:
ApiKeyRequired: false
AuthorizationType: NONE
HttpMethod: GET
Integration:
IntegrationHttpMethod: POST
Type: AWS_PROXY
Uri:
!Join
- ''
- - 'arn:'
- !Ref AWS::Partition
- ':apigateway:'
- !Ref AWS::Region
- :lambda:path/2015-03-31/functions/
- !GetAtt HelloWorldFunction.Arn
- /invocations
ResourceId: !Ref HelloWorldApiGatewayResource
RestApiId: !Ref RestApi
HelloWorldFunctionApiGatewayPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !GetAtt HelloWorldFunction.Arn
Principal: apigateway.amazonaws.com
SourceArn:
!Join
- ''
- - 'arn:'
- !Ref AWS::Partition
- ':execute-api:'
- !Ref AWS::Region
- ':'
- !Ref AWS::AccountId
- ':'
- !Ref RestApi
- /*/*
HelloWorldFunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
Policies:
- PolicyName: AppendToLogsPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
HelloWorldFunction:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: |
exports.handler = async (event) => {
return {
isBase64Encoded: false,
statusCode: 200,
headers: { "content-type": "text/plain"},
body: "Hello World!"
}
}
Handler: index.handler
Role: !GetAtt HelloWorldFunctionRole.Arn
Runtime: nodejs14.x
# Route 2: ... copy the above block to make another! ...
# Outputs the URL to our Lambda REST API.
Outputs:
ApiUrl:
Description: URL of the REST API.
Value:
Fn::Join:
- ''
- - https://
- Ref: RestApi
- .execute-api.
- Ref: AWS::Region
- .
- Ref: AWS::URLSuffix
- /dev
To deploy the REST API stack:
aws cloudformation create-stack \
--stack-name rest-api-example \
--template-body file://rest-api-example.yml \
--capabilities CAPABILITY_NAMED_IAM
To monitor the REST API's deployment:
aws cloudformation describe-stack-events \
--stack-name rest-api-example
After it's deployed, you can get your Lambda's public URL with:
aws cloudformation describe-stacks \
--stack-name rest-api-example \
--query "Stacks[0].Outputs[?OutputKey=='ApiUrl'].OutputValue" \
--output text
Append /hello_world
to the URL, and hit it!
AWS CloudFormation Lambda Trigger Examples
To invoke your function in response to events – i.e. raised by other AWS services – create a trigger from one of these examples:
SQS Lambda Trigger CloudFormation Example
To invoke your function in response to SQS events:
Resources:
LambdaFunction:
Type: 'AWS::Lambda::Function'
Properties:
Handler: index.handler
Runtime: nodejs14.x
Role: !GetAtt LambdaFunctionRole.Arn
Code:
ZipFile: |
exports.handler = async (sqsEvent) => {
console.log(JSON.stringify(sqsEvent));
}
LambdaFunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
Policies:
- PolicyName: LambdaFunctionRolePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
- Effect: Allow
Action:
- sqs:ReceiveMessage
- sqs:DeleteMessage
- sqs:GetQueueAttributes
Resource: YOUR_SQS_QUEUE_ARN # CHANGE THIS
LambdaFunctionSqsEventSourceMapping:
Type: AWS::Lambda::EventSourceMapping
DependsOn:
- LambdaFunctionRole
Properties:
BatchSize: 10
Enabled: true
EventSourceArn: YOUR_SQS_QUEUE_ARN # CHANGE THIS
FunctionName: !GetAtt LambdaFunction.Arn
Note: replace YOUR_SQS_QUEUE_ARN
with the ARN of your SQS queue.
S3 Lambda Trigger CloudFormation Example
To invoke your function in response to S3 events (i.e. object creations and deletions):
Resources:
LambdaFunction:
Type: 'AWS::Lambda::Function'
Properties:
Handler: index.handler
Runtime: nodejs14.x
Role: !GetAtt LambdaFunctionRole.Arn
Code:
ZipFile: |
exports.handler = async (s3Event) => {
console.log(JSON.stringify(s3Event));
}
LambdaFunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
Policies:
- PolicyName: LambdaFunctionRolePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
# Allows S3 to call our Lambda function.
LambdaFunctionS3Permission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !GetAtt LambdaFunction.Arn
Principal: s3.amazonaws.com
SourceAccount: !Ref AWS::AccountId
SourceArn:
!Join
- ''
- - 'arn:'
- !Ref AWS::Partition
- ':s3:::'
- my-bucket # CHANGE THIS
# Create our S3 bucket, pre-configured to invoke our Lambda.
S3Bucket:
Type: AWS::S3::Bucket
DependsOn:
- LambdaFunctionS3Permission
Properties:
BucketName: my-bucket # CHANGE THIS
NotificationConfiguration:
LambdaConfigurations:
- Event: s3:*
Function: !GetAtt LambdaFunction.Arn
Note: replace my-bucket
with a unique bucket name.
SNS Lambda Trigger CloudFormation Example
To invoke your function in response to SNS topic events:
Resources:
LambdaFunction:
Type: 'AWS::Lambda::Function'
Properties:
Handler: index.handler
Runtime: nodejs14.x
Role: !GetAtt LambdaFunctionRole.Arn
Code:
ZipFile: |
exports.handler = async (snsEvent) => {
console.log(JSON.stringify(snsEvent));
}
LambdaFunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
Policies:
- PolicyName: LambdaFunctionRolePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
# Allows SNS to call our Lambda function.
LambdaFunctionSNSPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !GetAtt LambdaFunction.Arn
Principal: sns.amazonaws.com
SourceArn:
!Join
- ''
- - 'arn:'
- !Ref AWS::Partition
- ':sns:'
- !Ref AWS::Region
- ':'
- !Ref AWS::AccountId
- ':'
- YOUR_SNS_TOPIC_NAME # CHANGE THIS
# Create our SNS topic, pre-configured to invoke our Lambda.
SNSTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: YOUR_SNS_TOPIC_NAME # CHANGE THIS
Subscription:
- Endpoint: !GetAtt LambdaFunction.Arn
Protocol: lambda
Note: replace YOUR_SNS_TOPIC_NAME
with a unique topic name.
Kinesis Lambda Trigger CloudFormation Example
To invoke your function as a Kinesis record consumer:
Resources:
LambdaFunction:
Type: 'AWS::Lambda::Function'
Properties:
Handler: index.handler
Runtime: nodejs14.x
Role: !GetAtt LambdaFunctionRole.Arn
Code:
ZipFile: |
exports.handler = async (kinesisEvent) => {
console.log(JSON.stringify(kinesisEvent));
}
LambdaFunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
Policies:
- PolicyName: LambdaFunctionRolePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
- Effect: Allow
Action:
- kinesis:GetRecords
- kinesis:GetShardIterator
- kinesis:DescribeStream
- kinesis:ListStreams
Resource: YOUR_KINESIS_STREAM_ARN # CHANGE THIS
LambdaFunctionKinesisEventSourceMapping:
Type: AWS::Lambda::EventSourceMapping
DependsOn:
- LambdaFunctionRole
Properties:
BatchSize: 10
Enabled: true
StartingPosition: TRIM_HORIZON
EventSourceArn: YOUR_KINESIS_STREAM_ARN # CHANGE THIS
FunctionName: !GetAtt LambdaFunction.Arn
Note: replace YOUR_KINESIS_STREAM_ARN
with the ARN of your Kinesis stream.
DynamoDB Lambda Trigger CloudFormation Example
To invoke your function in response to DynamoDB events (i.e. item deletions, updates and creations):
Resources:
LambdaFunction:
Type: 'AWS::Lambda::Function'
Properties:
Handler: index.handler
Runtime: nodejs14.x
Role: !GetAtt LambdaFunctionRole.Arn
Code:
ZipFile: |
exports.handler = async (dynamoDbEvent) => {
console.log(JSON.stringify(dynamoDbEvent));
}
LambdaFunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
Policies:
- PolicyName: LambdaFunctionRolePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
- Effect: Allow
Action:
- dynamodb:GetRecords
- dynamodb:GetShardIterator
- dynamodb:DescribeStream
- dynamodb:ListStreams
Resource: YOUR_DYNAMODB_STREAM_ARN # CHANGE THIS
LambdaFunctionDynamoDbEventSourceMapping:
Type: AWS::Lambda::EventSourceMapping
DependsOn:
- LambdaFunctionRole
Properties:
BatchSize: 10
Enabled: true
StartingPosition: TRIM_HORIZON
EventSourceArn: YOUR_DYNAMODB_STREAM_ARN # CHANGE THIS
FunctionName: !GetAtt LambdaFunction.Arn
Note: replace YOUR_DYNAMODB_STREAM_ARN
with the "Stream ARN" of your DynamoDB table. You can access this with !GetAtt DynamoDbTable.StreamArn
.
Final Thoughts
You can find each of these examples in the following GitHub repository:
https://github.com/upload-io/cloudformation-lambda-examples
While examples are great, nothing beats reading the official CloudFormation documentation – it's possibly some of the clearest, most composable documentation there is.
We recommend starting by taking one of our Lambda CloudFormation examples, and then extending it by searching the "resource type" in Google (which will land you on the official AWS docs), and then exploring all the possible fields for that resource type.
Happy coding!