Designing an API on AWS is hard. Designing a reliable API on AWS is even harder. The AWS Serverless Application Model (SAM) is an open source serverless framework that makes deploying serverless resources much easier. SAM extends CloudFormation, so you can use all the usual CloudFormation resources inside of a SAM template. SAM is a full-featured serverless can deploy serverless functions, databases, and APIs.
In this post I’ll be covering the basics of using the AWS SAM template language, the AWS SAM CLI, and some advanced features like usage plans and API keys.
The SAM template language and the SAM CLI
SAM is composed of two parts, the SAM template language and the SAM CLI. The SAM template language provides a method of describing serverless resources in JSON or YAML blocks. The SAM template language provides seven resource types.
AWS::Serverless::Api
AWS::Serverless::Application
AWS::Serverless::Function
AWS::Serverless::HttpApi
AWS::Serverless::LayerVersion
AWS::Serverless::SimpleTable
AWS::Serverless::StateMachine
For example, I can define a Lambda function using an AWS::Serverless::Function
block. Here I’ve created a Lambda function using python3.6
and SAM will store the code in an S3 bucket.
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
Runtime: python3.6
CodeUri: s3://bucket/key
The SAM CLI provides a command-line interface for building, testing, and deploying the serverless resources described by your SAM templates. The SAM CLI has eight commands.
sam build
– Processes your template file, prepares code and dependencies for deploymentsam deploy
– Deploys resources described in SAM templatesam init
– Initializes a new SAM projectsam local
– Creates local version of serverless resources for easier testing and debuggingsam logs
– Show your resources’s logssam package
– Creates a zip and uploads an artifact to S3sam validate
– Validates template file is valid CloudFormation syntaxsam publish
– Publishes an application to the Serverless Application Repository
Start a SAM project with the SAM CLI
Let’s get started building a simple API that takes two URL parameters, a
and b
, and returns their product
.
AWS SAM has a CLI that makes creating a new project simple. Install the AWS SAM CLI and then use sam init
to start a new project. I like to use the quick start templates to get a project going quickly, but you can also select 2
for a Custom Template Location and give it a filepath or URL.
sam init
Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice: 1
Choose a language for your project’s runtime. I’ll be using python3.6
.
Which runtime would you like to use?
1 - nodejs12.x
2 - python3.8
3 - ruby2.7
4 - go1.x
5 - java11
6 - dotnetcore3.1
7 - nodejs10.x
8 - python3.7
9 - python3.6
10 - python2.7
11 - ruby2.5
12 - java8
13 - dotnetcore2.1
14 - dotnetcore2.0
15 - dotnetcore1.0
Runtime: 9
Name the project and select the Hello World template. This will generate a sample project with a Lambda function and a template for an API Gateway resource.
Project name [sam-app]: SAMdemo
Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git
AWS quick start application templates:
1 - Hello World Example
2 - EventBridge Hello World
3 - EventBridge App from scratch (100+ Event Schemas)
4 - Step Functions Sample App (Stock Trader)
5 - Elastic File System Sample App
Template selection: 1
-----------------------
Generating application:
-----------------------
Name: SAMdemo
Runtime: python3.6
Dependency Manager: pip
Application Template: hello-world
Output Directory: .
Next steps can be found in the README file at ./SAMdemo/README.md
SAM generates a bunch of boilerplate code and a few folders for you. There are four main parts to know.
template.yaml
– Defines your SAM resourceshello_world/
– Directory for Lambda function codetests/
– Directory for unit tests for your function, run using Pytestevents/
– Mocked events for testing functions
Create a Lambda function
We need to create a function that can take in two URL parameters and return a product. I will reuse the hello_world
function the SAM CLI generated by renaming the hello_world
folder multiply
and editing the app.py
file inside it.
mv hello_world/ multiply/
vim multiply/app.py
The app.py
file defines the Lambda function. This function will take in two integers, a
and b
, as URL parameters and return their product. We can access the URL parameters through the event
variable. The event
variable contains data about the API Gateway event that triggered this Lambda function.
import json
def lambda_handler(event, context):
'''<snip>'''
a = event["queryStringParameters"]['a']
b = event["queryStringParameters"]['b']
product = int(a) * int(b)
return {
"statusCode": 200,
"body": json.dumps({
"product": product,
}),
}
Lambda functions must return a JSON response, so I’ve chosen to just dump the product
result into the body
of the response.
Define an API Gateway and connect to Lambda using a SAM template
SAM also generated a file called template.yaml
. I will tell SAM that I want to deploy a Lambda function by including an AWS::Serverless:Function
block inside the SAM template. In the same block, I’ll include a link to the API Gateway MultiplyAPI
to trigger the function with API Gateway.
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Function and API for multiplying two numbers
Resources:
MultiplyFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: multiply/
Handler: app.lambda_handler
Runtime: python3.6
Events:
Multiply:
Type: Api
Properties:
Path: /multiply
Method: POST
RestApiId:
Ref: MultiplyApi
Outputs:
MultiplyApi:
Description: "API Gateway endpoint URL for Prod stage for Multiply function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/multiply/"
MultiplyFunction:
Description: "Multiply Lambda Function ARN"
Value: !GetAtt MultiplyFunction.Arn
MultiplyFunctionIamRole:
Description: "Implicit IAM Role created for Multiply function"
Value: !GetAtt MultiplyFunctionRole.Arn
This template will deploy a Lambda function backed by the code in the multiply
folder. After the deployment, the Outputs
section will return an API Gateway Endpoint, a Lambda function ARN, and an IAM role for the function.
Deploy the SAM template with the SAM CLI
It is time to deploy the SAM template. I’ll be using the SAM CLI’s sam deploy --guided
command to start an interactive session which will guide me through the deployment process.
~/SAMdemo ❯ sam deploy --guided
Configuring SAM deploy
======================
Looking for samconfig.toml : Not found
Setting default arguments for 'sam deploy'
=========================================
Stack Name [sam-app]: SAMDemo
AWS Region [us-east-1]:
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [y/N]: N
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: Y
MultiplyFunction may not have authorization defined, Is this okay? [y/N]: Y
Save arguments to samconfig.toml [Y/n]: Y
After you finish the guided process, SAM will generate a change set (Just like CloudFormation) and deploy your resources. You will see the outputs
from template.yaml
printed onto the screen.
CloudFormation outputs from deployed stack
-------------------------------------------------------------------------------------------------
Outputs
-------------------------------------------------------------------------------------------------
Key MultiplyFunctionIamRole
Description Implicit IAM Role created for Multiply function
Value arn:aws:iam::<snip>
Key MultiplyFunction
Description Multiply Lambda Function ARN
Value arn:aws:lambda:us-east-1:<snip>
Key MultiplyApi
Description API Gateway endpoint URL for Prod stage for Multiply function
Value https://<snip>.execute-api.us-east-1.amazonaws.com/Prod/multiply/
-------------------------------------------------------------------------------------------------
Successfully created/updated stack - SAMDemo in us-east-1
Testing the API with SAM local
SAM provides a method for testing your API locally prior to deployment called sam local
. We can use it to generate mock API events and use them to test locally on a development machine.
First, let’s generate a mock API Gateway event. The command sam local generate-event apigateway aws-proxy > events/multiply.json
will generate a fake API event and save it to a JSON file. Change the queryStringParameters
in multiply.json
to some integers for a
and b
.
"queryStringParameters": {
"foo": "bar"
},
"queryStringParameters": {
"a": "5",
"b": "3"
}
Now lets use sam local invoke
to invoke the Lambda function and provide it the multiply
event.
sam local invoke -e multiply.json
Invoking app.lambda_handler (python3.8)
Fetching lambci/lambda:python3.8 Docker container image......
Mounting /Users/seanziegler/Coding/SAMdemo/multiply as /var/task:ro,delegated inside runtime container
START RequestId: a17fdb0c-15bc-1cb0-c8a5-836299856b0c Version: $LATEST
END RequestId: a17fdb0c-15bc-1cb0-c8a5-836299856b0c
REPORT RequestId: a17fdb0c-15bc-1cb0-c8a5-836299856b0c Init Duration: 93.96 ms Duration: 2.85 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 25 MB
{"statusCode":200,"body":"{\"product\": 15}"}
The function returned product: 15
which is what we expected. You can use sam local generate event
to generate mock events for many services including S3, APIGateway, DynamoDB, SQS, and more. Generating events and testing locally is a great pattern for ensuring your serverless functions are reliable.
Adding API Gateway Authentication with SAM
Adding API keys and a usage plan to an API is a straightforward process. It’s possible to set up both using the Auth
object on AWS::Serverless::Api
.
On the Multiply route I will require an API key, limit requests to 500 per day, and limit requests to 5 requests per second.
MultiplyApi:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
Auth:
ApiKeyRequired: true
UsagePlan:
CreateUsagePlan: PER_API
Quota:
Limit: 500
Period: DAY
Throttle:
RateLimit: 5
Let’s make a request and see what happens.
curl -X POST https://<snip>.execute-api.us-east-1.amazonaws.com/Prod/multiply?a=3&b=1
{"message": "Missing Authentication Token"}
Okay, that didn’t work because I didn’t supply an API key.
Let’s try it again with an API key I generated for myself earlier.
curl -X POST -H "x-api-key:<API KEY>" https://<snip>.execute-api.us-east-1.amazonaws.com/Prod/multiply?a=5&b=3
{"product": 15}
Conclusion
That’s all it takes to build a small API using SAM. You can extend this API by adding more routes and functions as resource declarations in the template.yaml
file. The SAM template language reference and the SAM CLI reference are your friends.
4 comments
Reason: Unresolved resource dependencies [ServerlessRestApi] in the Outputs block of the template
Tried to follow your code but its failing with above issue ..can you help if am i missing something or could you please provide template.yaml code completely ?
You can hard code the name for the MultiplyApi in place of the ${foo}. Also the template requires creation of the API resource. Otherwise a good read.
Apologies, I was mistaken with the hard coding.
Add this in the Resources section:
MultiplyApi:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
Name: MultiplyApi
Then in the outputs section:
Value: !Sub “https://${MultiplyApi}.${AWS::Region}.amazonaws.com/Prod/multiply/”
Should be good to go then. Cheers.
Thank you for this write up, I had been searching for an example with Type: AWS::Serverless::Api
Here are some issues I had, perhaps they will save others from same problems I encountered. (2020_10Oct_25)
[1] For the first deploy, the RestApiId, prevented deployment, since it is not defined until the MultipyApi resource is added for the second deploy.
RestApiId: Ref: MultiplyApi
I just removed it, then added it back for the second deploy
[2] Super minor cut and paste issue, in the multiply.json file, if using the copy button in blog, you will need to add the comma after the ‘}’ in queryStringParameters value specification.
[3] since I didn’t change directories after the ‘sam local generate-event … > events/multiply.json’, I need to change the -e on then next command to include the events subdirectory
sam local invoke -e multiply.json
[4] I needed to add ‘Name: MultiplyApi’ to MulitiplyApi resource as in Vince’s Oct 3 comment
[5] I needed to add ‘execute-api’ to the output change suggested by Vince’s Oct 3 comment
Value: !Sub "https://${MultiplyApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/multiply/"
[6] I needed to add a BurstLimit to Throttle, it was defaulting to zero, and giving me limit exceeded errors
Throttle: RateLimit: 5 BurstLimit: 15
[7] on the curl command, I need to put the URL in double quotes, otherwise it was using & in URL as command line termination and since I wasn’t then passing a b parameter, I was getting an Internal Server Error