The application consists of an API Gateway, a Lambda function, and a Simple Notification Service (SNS) topic. When the API Gateway receives an HTTP request, it triggers the Lambda function, the Lambda function publishes a message to an SNS topic, which results in an emails being sent to all the subscribers of the topic.

The complete code of the application is available on GitHub.

AWS Lambda Basics

First of all, some important basics.

Lambda Functions vs. Lambda Applications

A Lambda function is a piece of code (managed by AWS) that is executed whenever it is triggered by an event from an event source. A Lambda application is a cloud application that includes one ore more Lambda functions, as well as potentially other types of services.

In almost all cases, a Lambda application contains multiple types and instances of services. For example, the Lambda application depicted above consists of a Lambda function, an API Gateway, and an SNS topic.

Lambda Execution Model

A Lambda function is triggered by a so-called event source. An event source is an AWS service. Currently there are more than a dozen AWS services that can be used as event sources for Lambda functions.

AWS Lambda execution model.

AWS services that can act as event sources for Lambda functions include API Gateway, S3, DynamoDB, SNS, SQS, Cognito, IoT, Kinesis, among others. A Lambda function can have any number of event sources.

Once a Lambda function is running, it can basically do anything you want. Very often, a Lambda function uses other AWS services (which can then potentially trigger yet another Lambda function). However, Lambda functions are not restricted to that, they can execute any code you want. For example, you could use an AWS Lambda function to create or update a Kubernetes cluster on Google Cloud Platform (GCP).

Lambda Runtimes

AWS provide a limited set of runtime environments for Lambda functions. They currently include the following (see docs):

Node.js (8.10 and 6.10)

Java (8)

Python (3.6 and 2.7)

Go

.NET C# (2.1, 2.0, and 1.0)

That means that you have to write the code for your Lambda functions in one of these languages.

What is SAM?

This is SAM showing you what it can do for you (from GitHub).

Back to our main topic. If you are familiar with AWS and CloudFormation, then SAM is simply an extension of CloudFormation.

CloudFormation

If you don’t know CloudFormation, then it’s important to know that CloudFormation is the Infrastructure as Code (IaC) solution of AWS. It is in the same category of tools as Terraform (a very good comparison between CloudFormation and Terraform can be found here).

With CloudFormation you can declaratively specify the AWS infrastructure that you need for your cloud application in a YAML or JSON file, called a template, and then deploy this template. During the deployment, AWS automatically provisions all the resources that you specified in your template.

If you already deployed before and just made changes to your template, then AWS calculates a change set and applies it, so that the state of your infrastructure matches your the specification (just like Kubernetes).

SAM Extends CloudFormation

SAM is just CloudFormation under the hood. With SAM you specify the resources for your Lambda application in a SAM template, and then you deploy this template. In fact, before deployment, your SAM template is automatically transformed to a CloudFormation template, which is then deployed.

SAM adds a few more resource types that are not present in CloudFormation. Most importantly this is the AWS::Serverless::Function resource type, which allows to concisely define Lambda functions. This resource type has all the required properties for a Lambda function, including a list of possible event types that serve as the event sources for the Lambda function.

The syntax specification of SAM can be found here: version 2016–10–31.

SAM Template

A minimal SAM template (in YAML) looks like this (this is typically saved in a file called template.yml ):

AWSTemplateFormatVersion : '2010-09-09'

Transform: AWS::Serverless-2016-10-31

Resources:

HelloWorldFunction:

Type: AWS::Serverless::Function

Properties:

Handler: index.handler

Runtime: nodejs8.10

This template specifies a Lambda application that consists of a single resource. This resource is a Lambda function (called HelloWorldFunction ) that uses the Node.js 8.10 runtime, and the code for this Lambda function is in the file index.js in the handler function.

Note the property Transform: AWS::Serverless-2016-10-31 . This specifies the format of this template and tells CloudFormation how to interpret it so that it can transform it to a standard CloudFormation template that can then be deployed to AWS.

In fact, you can deploy this SAM template as you would deploy any CloudFormation template with the AWS CLI. However, there’s an even better way, as SAM also provides its own CLI.

Lambda Function Code

Along with the SAM template, you have to define the code for your Lambda functions. The exact format of the entry points of this code depends on the used Lambda runtime. For Node.js 8.10, a minimal code file looks like this:

exports.handler = async function(event, context) {

return 'Hello World!';

};

This code can, for example, be saved in a file called index.js in the same directory as the template file. You can choose anything you want for the file name and the name of the exported handler function (in this case handler ), but you have to make sure that it coincides with the Handler property of the Lambda function in the template (for example, in this case, the property must be set to Handler: index.handler ).

The required formats of the handler functions for the different supported programming languages are described here in the AWS docs.

SAM CLI

SAM provides its own CLI that, while not mandatory, is quite convenient when working with SAM. The GitHub repository of this CLI is here.

You can install the SAM CLI simply with:

pip install aws-sam-cli

The CLI will then be available as sam .

Deploying with the SAM CLI

You can use the SAM CLI to deploy your SAM template to AWS. The deployment consists of two (or potentially three) steps:

1. Package

sam package \

--template-file template.yml \

--output-template-file package.yml \

--s3-bucket my-bucket

The purpose of the package command is to upload any artifacts that your Lambda application requires to an AWS S3 bucket. When you later deploy your Lambda application to AWS, the artifacts will be automatically retrieved from this S3 bucket.

In the case of a Lambda function, the most important artifact is of course the code for the Lambda function (including all the dependencies). The result of above command is that your entire project directory (that includes the code) will be zipped and uploaded to the S3 bucket.

The command also produces an output file called package.yml . This is a copy of your template.yml template file, but with the CodeUri property of each Lamda function in the template set to URI of the uploaded package on the S3 bucket.

It is this package.yml file that you will deploy to AWS in the next step. Thanks to the CodeUri properties int this file, AWS will be able to locate and retrieve the code for each Lambda function from the S3 bucket.

You can specify any S3 bucket you want for the package command, and you can use the same bucket multiple times and for multiple applications. If you don’t have an S3 bucket yet, you can easily create one in the AWS Console or with the AWS CLI with aws s3 mb s3://my-bucket . This is the potential third step of the deployment process.

2. Deploy

sam deploy \

--template-file package.yml \

--stack-name my-sam-application \

--capabilities CAPABILITY_IAM

This command deploys your template defined in the package.yml file to AWS. Since SAM is based on CloudFormation, this command creates a new, or updates an existing, CloudFormation stack. You have to specify the name of this CloudFormation stack with the --stack-name option.

The --capabilities CAPABILITY_IAM option is necessary to authorise your stack to create IAM roles, which SAM applications do by default.

Note:

The sam package and sam deploy commands are really just aliases for the aws cloudformation package and aws cloudformation deploy commands. So, if you don’t have the SAM CLI installed, you can just use these latter commands instead of the former ones.

If you want to see the full usage for sam package and sam deploy , you can find it under aws cloudformation package help and aws cloudformation deploy help , respectively.

Deleting a Lambda Application

The SAM CLI does not provide a command for deleting a Lambda application. However, since a Lambda application is just a CloudFormation stack, you can delete it by deleting the CloudFormation stack with the AWS CLI:

aws cloudformation delete-stack --stack-name my-sam-application

SAM Alternatives

SAM is not the only tool that allows to define and deploy Lambda applications to AWS. A notable alternative is the Serverless Framework.The main difference between SAM and the Serverless Framework is that the latter is not specific to AWS but can be used with the various cloud providers. A great comparison between SAM and the Serverless Framework can be found here.

Terraform can also be used to deploy serverless applications to AWS.

Depending on your use case, the fact that the latter tools can be used for multiple cloud providers might be an advantage. However, if you are anyway going for AWS, then SAM is a great solution as it provides optimised integrations with AWS concepts, such as IAM permissions, which can result in simpler templates.

Building a Lambda Application with SAM

Let’s now build the Lambda application that I introduced at the beginning of this article. Here is the architecture diagram again:

The final Lambda application.

The application allows to make HTTP requests to a specific API endpoint upon which an email is sent to a specific email address.

In the following, we are going to build this application in three steps:

We are going to use the Node.js 8.10 as the runtime for the Lambda function. That is, the Lambda function code will be written in JavaScript.

The code for all three steps is available on GitHub.

Step 1: Single Lambda Function

As a first step, we are going to create an intermediary Lambda application that consists solely of a single Lambda function, nothing else. This is how it will look like:

Stage 1 of the Lambda application.

The Code

Create a new directory with the following files:

sam-hello-world/

|-- template.yml

|-- index.js

And the content of these files is as follows:

template.yml :

AWSTemplateFormatVersion : '2010-09-09'

Transform: AWS::Serverless-2016-10-31

Resources:

HelloWorldFunction:

Type: AWS::Serverless::Function

Properties:

Handler: index.handler

Runtime: nodejs8.10

index.js :

exports.handler = async function(event, context) {

return 'Hello World!';

};

The template defines a Lambda application that contains a single Lambda function called HelloWorldFunction . When this function is triggered, the handler function in index.js is executed, which simply returns the string Hello World! .

Deploy It

Let’s deploy this application to AWS with the SAM CLI:

sam package \

--template-file template.yml \

--output-template-file package.yml \

--s3-bucket my-bucket sam deploy \

--template-file package.yml \

--stack-name sam-hello-world-1 \

--capabilities CAPABILITY_IAM

When the sam deploy completes, our Lambda application is up and running on AWS.

Note: if you never used the AWS CLI before, you should install and configure it now, because the SAM CLI depends on the configuration and saved credentials of the AWS CLI. Install the AWS CLI with pip install awscli and configure it with aws configure . This will create the files ~/.aws/config and ~/.aws/credentials which are also used by the SAM CLI.

AWS Console: Lambda

You can check that the application has indeed been deployed by navigating to the Lambda service in the AWS Console at https://console.aws.amazon.com/lambda/ and clicking on Applications.

There, you should see your brand-new Lambda application called sam-hello-world-1 :

If you click on the application, you see all the resources that this application contains. In our case, this is just a single Lambda function called HelloWorldFunction :

If you click on the function, you see all the details about this function. There you will also meet again the fiddly text editor, with your function’s code pre-loaded, that, thanks to SAM, you do not have to use:

AWS Console: CloudFormation

As mentioned, deploying a Lambda application with SAM creates (or updates) a CloudFormation stack. You can see the stack that has been created for your Lambda application by navigating to the CloudFormation service in the AWS Console at https://console.aws.amazon.com/cloudformation/.

There, you should see the stack called sam-hello-world-1 that corresponds to your Lambda application (you specified this name in the sam deploy command):

If you click on the stack, you see the exact set of resources that it contains:

As you can see, the stack contains a Lambda function and an IAM role. These are all the resources that make up your current Lambda application.

So, we can now be confident that our Lambda application has indeed been correctly deployed to AWS. Now, let’s extend it.

Step 2: Adding an Event Source

The Lambda application created in the previous step consists of a single Lambda function that does not have any event source associated. This means that this Lambda function will never be triggered by an event, which means that it will never run.

Let’s change this by setting things up so that the Lambda function is triggered whenever a user makes an HTTP request to a specific API endpoint. This is how our extended application will look like:

Stage 2 of the Lambda application.

The Code

Change the two files source files in your project as follows.

template.yml :

AWSTemplateFormatVersion : '2010-09-09'

Transform: AWS::Serverless-2016-10-31

Resources:

HelloWorldFunction:

Type: AWS::Serverless::Function

Properties:

Handler: index.handler

Runtime: nodejs8.10

Events:

HelloWorldApi:

Type: Api

Properties:

Path: /

Method: GET

index.js :

exports.handler = async function(event, context) {

return { statusCode: 200, body: 'Hello World!' };

};

Explanations: template.yml

For template.yml , the only difference to the previous version is the addition of the Events property to the Lambda function (highlighted in bold above).

The Events property defines an event source for our Lambda function. We define this event source to be an API ( Type: Api ), and set things up so that events are emitted, and our Lambda function triggered, whenever a GET request to the / endpoint of this API is made.

When we later deploy this application to AWS, this template will cause an API Gateway API to be created and associated with our Lambda application.

Note:

How do we know what types of event sources exist? And, in general, how do we know about the syntax, available properties, et cetera, of the template file? The answer is, by the SAM specification. It defines everything we need to know to create a SAM template. For example, all the available event sources are listed and described here.

Explanations: index.js

Regarding index.js , the difference to the previous version is that we return not a string, but an object with a statusCode and body property.

This is necessary because of the way the API Gateway event source is implemented. It uses a proxy that expects an object of this form from the Lambda function. The proxy will then use the statusCode value as the status code of the HTTP response and the body value as the payload of the response.

In effect, this means that the caller of the API Gateway endpoint will receive an HTTP response with status code 200 and a Hello World! body. In fact, this shows that the Lambda function has been correctly triggered and executed.

Deploy It

Let’s deploy our new Lambda application as sam-hello-world-2 :

sam package \

--template-file template.yml \

--output-template-file package.yml \

--s3-bucket my-bucket sam deploy \

--template-file package.yml \

--stack-name sam-hello-world-2 \

--capabilities CAPABILITY_IAM

If you want, you can also use the same name as the previous version, sam-hello-world-1 , instead of sam-hello-world-2 , in which case the previous version of the application will just be overwritten.

Triggering the Function

If you go now to the Lambda service in the AWS Console under https://console.aws.amazon.com/lambda/, you should see your new Lambda application sam-hello-world-2 there.

If you select the application, you can see that it contains an additional resource, namely the API Gateway:

If you select the Lambda function, you see that API Gateway has already ben added as an event source for the Lambda function. If you click on the API Gateway event source, you can see the API endpoint URL that will trigger the Lambda function:

Go on and open this URL in your browser or make a request with curl . You should receive back an HTTP response with status code 200 and a Hello World! body. This means that your Lambda function has been successfully triggered and executed!

CloudWatch Logs

What if you want to know more details about when and how your Lambda function is executed? This is no problem, because AWS Lambda automatically creates CloudWatch logs for every Lambda function.

These logs, by default, include start and end events for each execution of a Lambda function, as well as report statements that include the duration and used memory of each execution. Furthermore, everything that you write to stdout in your Lambda function code (e.g. with console.log ) will be sent to the CloudWatch logs too.

To see the CloudWatch, you can go to the function details in the AWS Console and click on the Monitoring tab. There you will see a button labelled View logs in CloudWatch:

Clicking the button takes you to the CloudWatch log group that has been created for this function. There you can see the full logs that were produced during the lifetime of the function:

So, now our Lambda application includes a Lambda function and an API Gateway as an event source. But the function does not do anything useful yet, it just returns a string to the caller of the API. Let’s add some action.

Step 3: Adding an Action

Our final Lambda application sends an email to a given email address whenever the API endpoint from the previous step receives a GET request.

We will implement this by making the Lambda function publish a message to a Simple Notification Service (SNS) topic, to which the target email address is subscribed.

This is how the final application will look like:

Stage 3 of the Lambda application (final).

The Code

Modify the two source files as follows.

template.yml :

AWSTemplateFormatVersion : '2010-09-09'

Transform: AWS::Serverless-2016-10-31

Resources:

HelloWorldFunction:

Type: AWS::Serverless::Function

Properties:

Handler: index.handler

Runtime: nodejs8.10

Events:

HelloWorldApi:

Type: Api

Properties:

Path: /

Method: GET

Policies:

- SNSPublishMessagePolicy:

TopicName: !GetAtt HelloWorldTopic.TopicName

Environment:

Variables:

SNS_TOPIC_ARN: !Ref HelloWorldTopic

HelloWorldTopic:

Type: AWS::SNS::Topic

Properties:

Subscription:

- Endpoint: youremail@example.com

Protocol: email

index.js :

const aws = require('aws-sdk');

aws.config.update({region: 'us-east-1'});

const sns = new aws.SNS() exports.handler = async function(event, context) { const params = {

Message: 'Hello World!',

Subject: 'SNS Notification from Lambda',

TopicArn: process.env.SNS_TOPIC_ARN

}; try {

await sns.publish(params).promise()

return { statusCode: 200, body: 'Message sent' };

} catch(err) {

return { statusCode: 500, body: JSON.stringify(err) };

}

};

Explanations: template.yml

There are quite some new lines in template.yml (highlighted in bold). But let’s look at it more closely. What has actually been added are two properties for the Lambda function, Policies and Environment , as well as a completely new resource which is named HelloWorldTopic .

SNS Topic:

Let’s look at the new HelloWorldTopic resource first. It is of type AWS::SNS::Topic , that is, it is an SNS topic. In effect, this means that when we deploy this Lambda application, a new SNS topic will be created. The topic also has a subscription defined right away that consists of an email address and uses the email protocol.

Note that you can’t find the resource type AWS::SNS::Topic in the SAM specification. So where does it come from? The answer is that it is a CloudFormation resource type rather than a SAM resource type. SAM is an superset of CloudFormation, and thus you can use classical CloudFormation resources in SAM templates.

You can find the specification of all available CloudFormation resource types here in the AWS docs. For example, the AWS::SNS::Topic resource type is specified here.

Environment:

Let’s look at the new Environment property for the HelloWorldFunction Lambda function. This property does nothing else than specifying an environment variable called SNS_TOPIC_ARN that will be available from the function’s source code (e.g. with process.env in Node.js).

The value of this environment variable is the ARN of the HelloWorldTopic SNS topic. The Lambda function code needs this ARN in order to publish a message to this topic, as we will see next.

Note that here we make use of another CloudFormation feature called intrinsic functions. Intrinsic functions are built-in CloudFormation functions that can be invoked from within CloudFormation (or SAM) templates. The full list of intrinsic functions is available here.

In our case, we use the Ref intrinsic function, which returns the ARN of the resource provided as argument. Here it is really necessary to use an intrinsic function, because at the time of the first deployment, the ARN of the new SNS topic (which is yet to be created) is simply not known.

Policies:

Finally, let’s look at the new Policies properties of the Lambda function. In short, this property grants permission to your Lambda function to publish messages to the HelloWorldTopic SNS topic.

This is necessary, because by default Lambda functions are assigned an IAM role that has no permissions at all (except to write to the CloudWatch logs). That means that for every AWS service that your Lambda function must access, you have to add the appropriate permissions to the Lambda function’s role.

In our case, we grant the required permissions to the function by using a SAM feature called policy templates. Policy templates are a set of predefined IAM policies that can be referenced by a simple string.

In our template we use the SNSPublishMessagePolicy policy template which matches our requirement of granting permission to publish messages to a specific SNS topic. This policy template requires a single argument named TopicName which must be the name of the SNS topic for which publishing permission should be granted. The specifications of all available SAM policy templates are available in this file.

Note that we use another CloudFormation intrinsic function GetAtt to retrieve the name of the HelloWorldTopic SNS topic. This is necessary, because this name is automatically generated and not known at the time of the initial deployment.

Explanations: index.js

The Lambda function code in index.js publishes a message to the application’s SNS topic (whose ARN the code retrieves from the SNS_TOPIC_ARN environment variable), and then returns a Message sent response to the caller of the API (or in case of an error while publishing to SNS, an error response).

In general, accessing AWS services from an AWS Lambda function is done in the same way as accessing AWS services from any other code, that is, with the AWS SDK. That means, the code that we use in our Lambda function to publish messages to an SNS topic is identical to the code that we would use in any other application.

The API reference for the AWS SDK for Node.js can be found here. The reference for the SNS publish method specifically is here.

A very convenient feature of AWS Lambda is that the AWS SDK dependency is available in all runtime environments by default. Thanks to this we can do require('aws-sdk') in our Node.js code without having to first do npm install aws-sdk . This keeps our Lambda application package slim, as no node_modules directory and package.json file is needed.

Deploy It

Let’s deploy our final Lambda application:

sam package \

--template-file template.yml \

--output-template-file package.yml \

--s3-bucket my-bucket sam deploy \

--template-file package.yml \

--stack-name sam-hello-world-3 \

--capabilities CAPABILITY_IAM

Confirm SNS Subscription

If you replaced the dummy email address in the template with your own email address, then, after the deployment completes, you should receive an email prompting you to confirm your subscription to the newly created SNS topic.

You have to do this, otherwise you will not receive emails for all the subsequent messages that are published to this topic. You have to do this only once at the very first deployment when the SNS topic (and your subscription) is newly created.

Test It

As in the previous step, retrieve the API endpoint URL of the Lambda application’s API Gateway and make a GET request to it, either with the browser or with curl . Observe your email inbox. After a few seconds, you should receive an email from SNS with your content.

At this point you successfully built an AWS Lambda application with a Lambda function, an event source, and an action that accesses another AWS service. And all this with SAM. Congratulations!