How to build an API with AWS Lambda and the Serverless Application Model (SAM)
Tags: devops, aws, tutorial, howto
Published: 2020-07-05 11:16:39

<!-- wp:paragraph -->
<p>Designing an API on AWS is hard. Designing a reliable API on AWS is even harder. The <a href="https://aws.amazon.com/serverless/sam/">AWS Serverless Application Model (SAM)</a> is an open source 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 can deploy serverless functions, databases, and APIs. <br></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>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.</p>
<!-- /wp:paragraph -->

<!-- wp:ub/table-of-contents-block {"blockID":"2ecee343-6417-45dd-8659-8ed100571177","title":"Table of Contents","links":"[{\u0022content\u0022:\u0022The SAM template language and the SAM CLL\u0022,\u0022level\u0022:2,\u0022anchor\u0022:\u00220-the-sam-template-language-and-the-sam-cll\u0022},{\u0022content\u0022:\u0022Start a SAM project with the SAM CLI\u0022,\u0022level\u0022:2,\u0022anchor\u0022:\u00221-start-a-sam-project-with-the-sam-cli\u0022},{\u0022content\u0022:\u0022Create a Lambda function\u0022,\u0022level\u0022:2,\u0022anchor\u0022:\u00222-create-a-lambda-function\u0022},{\u0022content\u0022:\u0022Define an API Gateway using a SAM template\u0022,\u0022level\u0022:2,\u0022anchor\u0022:\u00223-define-an-api-gateway-using-a-sam-template\u0022},{\u0022content\u0022:\u0022Deploy the SAM template with the SAM CLI\u0022,\u0022level\u0022:2,\u0022anchor\u0022:\u00224-deploy-the-sam-template-with-the-sam-cli\u0022},{\u0022content\u0022:\u0022Testing the API with generated events\u0022,\u0022level\u0022:2,\u0022anchor\u0022:\u00225-testing-the-api-with-generated-events\u0022},{\u0022content\u0022:\u0022Configuring an API Key and usage plan \u0022,\u0022level\u0022:2,\u0022anchor\u0022:\u00226-configuring-an-api-key-and-usage-plan-\u0022},{\u0022content\u0022:\u0022Conclusion\u0022,\u0022level\u0022:2,\u0022anchor\u0022:\u00227-conclusion\u0022}]","gaps":[1,1,1,1,1,1,1,1]} /-->

<!-- wp:heading -->
<h2 id="0-the-sam-template-language-and-the-sam-cll">The SAM template language and the SAM CLI</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>SAM is composed of two parts, the <a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-template-anatomy.html">SAM template language</a> and the <a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-command-reference.html">SAM CLI.</a> The SAM template language provides a method of describing serverless resources in JSON or YAML blocks. The SAM template language provides seven resource types.</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li><code>AWS::Serverless::Api</code></li><li><code>AWS::Serverless::Application</code></li><li><code>AWS::Serverless::Function</code></li><li><code>AWS::Serverless::HttpApi</code></li><li><code>AWS::Serverless::LayerVersion</code></li><li><code>AWS::Serverless::SimpleTable</code></li><li><code>AWS::Serverless::StateMachine</code></li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>For example, I can define a Lambda function using an <code>AWS::Serverless::Function</code> block. Here I’ve created a Lambda function using <code>python3.6</code> and SAM will store the code in an S3 bucket. </p>
<!-- /wp:paragraph -->

<!-- wp:code {"className":"lang-yaml"} -->
<pre class="wp-block-code lang-yaml"><code>Type: AWS::Serverless::Function
Properties:
  Handler: index.handler
  Runtime: python3.6
  CodeUri: s3://bucket/key</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>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.</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li><code>sam build</code> - Processes your template file, prepares code and dependencies for deployment</li><li><code>sam deploy</code> - Deploys resources described in SAM template</li><li><code>sam init</code> - Initializes a new SAM project</li><li><code>sam local</code> - Creates local version of serverless resources for easier testing and debugging</li><li><code>sam logs</code> - Show your resources's logs</li><li><code>sam package</code> - Creates a zip and uploads an artifact to S3</li><li><code>sam validate</code> - Validates template file is valid CloudFormation syntax</li><li><code>sam publish</code> - Publishes an application to the Serverless Application Repository</li></ul>
<!-- /wp:list -->

<!-- wp:heading -->
<h2 id="1-start-a-sam-project-with-the-sam-cli">Start a SAM project with the SAM CLI</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Let's get started building a simple API that takes two URL parameters, <code>a</code> and <code>b</code>, and returns their <code>product</code>.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>AWS SAM has a CLI that makes creating a new project simple. <a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html">Install the AWS SAM CLI</a> and then use <code>sam init</code> to start a new project. I like to use the quick start templates to get a project going quickly, but you can also select <code>2</code> for a Custom Template Location and give it a filepath or URL.</p>
<!-- /wp:paragraph -->

<!-- wp:code {"className":"language-bash"} -->
<pre class="wp-block-code language-bash"><code>sam init

Which template source would you like to use?
	1 - AWS Quick Start Templates
	2 - Custom Template Location

Choice: 1</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Choose a language for your project's runtime. I'll be using <code>python3.6</code>.</p>
<!-- /wp:paragraph -->

<!-- wp:code {"className":"language-bash"} -->
<pre class="wp-block-code language-bash"><code>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</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>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. </p>
<!-- /wp:paragraph -->

<!-- wp:code {"className":"language-bash"} -->
<pre class="wp-block-code language-bash"><code>Project name &#91;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</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>SAM generates a bunch of boilerplate code and a few folders for you. There are four main parts to know. </p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li><code>template.yaml</code> - Defines your SAM resources</li><li><code>hello_world/</code> - Directory for Lambda function code </li><li><code>tests/</code> - Directory for unit tests for your function, run using Pytest</li><li><code>events/</code> - Mocked events for testing functions</li></ul>
<!-- /wp:list -->

<!-- wp:heading -->
<h2 id="2-create-a-lambda-function">Create a Lambda function</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>We need to create a function that can take in two URL parameters and return a product. I will reuse the <code>hello_world</code> function the SAM CLI generated by renaming the <code>hello_world</code> folder <code>multiply</code> and editing the <code>app.py</code> file inside it.</p>
<!-- /wp:paragraph -->

<!-- wp:code {"className":"language-bash"} -->
<pre class="wp-block-code language-bash"><code>mv hello_world/ multiply/
vim multiply/app.py</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The <code>app.py</code> file defines the Lambda function. This function will take in two integers, <code>a</code> and <code>b</code>, as URL parameters and return their product. We can access the URL parameters through the <code>event</code> variable. <a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-services.html">The <code>event</code> variable</a> contains data about the API Gateway event that triggered this Lambda function.  </p>
<!-- /wp:paragraph -->

<!-- wp:code {"className":"language-python"} -->
<pre class="wp-block-code language-python"><code>import json

def lambda_handler(event, context):
    '''&lt;snip>'''

    a = event&#91;"queryStringParameters"]&#91;'a']
    b = event&#91;"queryStringParameters"]&#91;'b']

    product = int(a) * int(b)

    return {
        "statusCode": 200,
        "body": json.dumps({
            "product": product,
        }),
    }</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Lambda functions must return a JSON response, so I've chosen to just dump the <code>product</code> result into the <code>body</code> of the response.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 id="3-define-an-api-gateway-using-a-sam-template">Define an API Gateway using a SAM template</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>SAM also generated a file called <code>template.yaml</code>. I will tell SAM that I want to deploy a Lambda function by including an <code>AWS::Serverless:Function</code> block inside the SAM template.</p>
<!-- /wp:paragraph -->

<!-- wp:code {"className":"language-yaml"} -->
<pre class="wp-block-code language-yaml"><code>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</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>This template will deploy a Lambda function backed by the code in the <code>multiply</code> folder. After the deployment, the <code>Outputs</code> section will return an API Gateway Endpoint, a Lambda function ARN, and an IAM role for the function.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 id="4-deploy-the-sam-template-with-the-sam-cli">Deploy the SAM template with the SAM CLI</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>It is time to deploy the SAM template. <code>sam deploy --guided</code> starts an interactive session which guides you through the deployment process. </p>
<!-- /wp:paragraph -->

<!-- wp:code {"className":"language-bash"} -->
<pre class="wp-block-code language-bash"><code>~/SAMdemo ❯ sam deploy --guided                                                         

Configuring SAM deploy
======================

	Looking for samconfig.toml :  Not found

	Setting default arguments for 'sam deploy'
	=========================================
	Stack Name &#91;sam-app]: SAMDemo
	AWS Region &#91;us-east-1]: 
	#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
	Confirm changes before deploy &#91;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 &#91;Y/n]: Y
	MultiplyFunction may not have authorization defined, Is this okay? &#91;y/N]: Y
	Save arguments to samconfig.toml &#91;Y/n]: Y
</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>After you finish the guided process, SAM will generate a change set (Just like CloudFormation) and deploy your resources. You will see the <code>outputs</code> from <code>template.yaml</code> printed onto the screen. </p>
<!-- /wp:paragraph -->

<!-- wp:code {"className":"language-bash"} -->
<pre class="wp-block-code language-bash"><code>CloudFormation outputs from deployed stack
-------------------------------------------------------------------------------------------------
Outputs
-------------------------------------------------------------------------------------------------
Key                 MultiplyFunctionIamRole
Description         Implicit IAM Role created for Multiply function
Value               arn:aws:iam::&lt;snip>

Key                 MultiplyFunction
Description         Multiply Lambda Function ARN
Value               arn:aws:lambda:us-east-1:&lt;snip>
Key                 MultiplyApi
Description         API Gateway endpoint URL for Prod stage for Multiply function
Value               https://&lt;snip>.execute-api.us-east-1.amazonaws.com/Prod/multiply/
-------------------------------------------------------------------------------------------------

Successfully created/updated stack - SAMDemo in us-east-1</code></pre>
<!-- /wp:code -->

<!-- wp:heading -->
<h2 id="5-testing-the-api-with-generated-events">Testing the API with generated events</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>SAM provides a method for testing your API locally prior to deployment. First, let's generate a mock API Gateway event. The command <code>sam local generate-event apigateway aws-proxy &gt; events/multiply.json</code> will generate a fake API event and save it to a JSON file. Change the <code>queryStringParameters</code> in <code>multiply.json</code> to some integers for <code>a</code> and <code>b</code>. </p>
<!-- /wp:paragraph -->

<!-- wp:code {"className":"language-json"} -->
<pre class="wp-block-code language-json"><code>"queryStringParameters": {
    "foo": "bar"
  },</code></pre>
<!-- /wp:code -->

<!-- wp:code {"className":"language-json"} -->
<pre class="wp-block-code language-json"><code>  "queryStringParameters": {
    "a": "5",
    "b": "3"
  }</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Now lets use <code>sam local invoke</code> to invoke the Lambda function and provide it the <code>multiply</code> event.</p>
<!-- /wp:paragraph -->

<!-- wp:code {"className":"language-bash"} -->
<pre class="wp-block-code language-bash"><code>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}"}</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The function returned <code>product: 15</code> which is what we expected. You can use <code>sam local generate event</code> 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.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 id="6-configuring-an-api-key-and-usage-plan-">Configuring an API Key and usage plan </h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Adding API keys and a usage plan to an API is a straightforward process. It's possible to set up both using the <code>Auth</code> object on <code>AWS::Serverless::Api</code>. <br></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>On the Multiply route I will require an API key, limit requests to 500 per day, and limit requests to 5 requests per second. </p>
<!-- /wp:paragraph -->

<!-- wp:code {"className":"language-yaml"} -->
<pre class="wp-block-code language-yaml"><code>MultiplyApi:
     Type: AWS::Serverless::Api
     Properties:
       StageName: Prod
       Auth:
         ApiKeyRequired: true
         UsagePlan:
           CreateUsagePlan: PER_API
           Quota:
             Limit: 500
             Period: DAY
           Throttle:
             RateLimit: 5</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Let's make a request and see what happens. </p>
<!-- /wp:paragraph -->

<!-- wp:code {"className":"lang-bash"} -->
<pre class="wp-block-code lang-bash"><code>curl -X POST https://&lt;snip>.execute-api.us-east-1.amazonaws.com/Prod/multiply?a=3&amp;b=1

{"message": "Missing Authentication Token"}</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Okay, that didn't work because I didn't supply an API key. <br></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Let's try it again with an API key I generated for myself earlier.</p>
<!-- /wp:paragraph -->

<!-- wp:code {"className":"lang-bash"} -->
<pre class="wp-block-code lang-bash"><code>curl -X POST -H "x-api-key:&lt;API KEY>" https://&lt;snip>.execute-api.us-east-1.amazonaws.com/Prod/multiply?a=5&amp;b=3

{"product": 15}</code></pre>
<!-- /wp:code -->

<!-- wp:heading -->
<h2 id="7-conclusion">Conclusion</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>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 <code>template.yaml</code> file. The <a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-template-anatomy.html">SAM template language reference</a> and the <a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-command-reference.html">SAM CLI reference</a> are your friends.</p>
<!-- /wp:paragraph -->