AWS Serverless Application Model it's a great framework to start building serverless applications. With just a few lines of configuration, you can define the application you want and deploy it.
AWS SAM hides all the boilerplate configurations needed if building directly using AWS CloudFormation resources and properties.
While this is great for a quick start, it can become very quickly frustrating when you want to implement slightly more complex functionalities, such as validation, authorization, transformation, etc.
This repository is intended as a storage for various AWS SAM templates, covering different deployments scenarios.
Currently it holds sample templates for:
Deployment Scenario | SAM AWS template |
---|---|
Lambda Proxy Integration | template-lpi.yaml |
Lambda Proxy Integration with HTTP catch all | template-lpi-catch-all.yaml |
Lambda Proxy Integration with body content validation | template-lpi-validate.yaml |
Lambda Custom Integration | template-li.yaml |
Lambda Custom Integration with body content validation | template-li-validate.yaml |
In the future the list of sample templates will be extended with new deployment scenarios (incl transformation, authorization, etc).
Please refer to the following AWS documentation articles in order to understand the referenced concepts:
In order to run those examples you need to have SAM CLI installed and configured.
Please follow your platform specific installation instructions.
Following those installations instructions you will end-up with:
- an AWS account
- AWS / SAM CLI installed and configured locally using the above account
- an AWS S3 bucket which will be used during the deployment process
Any of the examples can be deployed using the following commands:
sam build -t template.yaml
sam package --template-file template.yaml --output-template-file packaged.yaml --s3-bucket your_bucket_name
sam deploy --template-file packaged.yaml --stack-name api-echo --capabilities CAPABILITY_IAM
where:
- template.yaml can be replaced with any of the sample templates file names
- api-echo can be replaced with any name that you want to be assigned to the deployed API
- your_bucket_name with the bucket name created when you've installed SAM CLI
All RESTs created by the sample templates are simple echo services. In general, they return the same information that is passeed in the body request. Particular implementation details are mentioned for each scenario.
APIs are powered by lambda functions. By default the runtime of lambda functions is nodejs10.x.
If you want to run the same examples using Java, just replace in the template the following configuration:
FunctionName: echo-lpi
CodeUri: EchoFunctionJS
Handler: app.lambdaHandler
Runtime: nodejs10.x
with:
CodeUri: EchoFunction
Handler: io.mindit.aws.handler.EchoHandler::handleRequest
Runtime: java8
MemorySize: 256
The simplest way to test the output of of the REST endpoint is by accessing the service in the API Gateway in the AWS console:
Template file: template-lpi.yaml
In a Lambda proxy integration, the entire client request is sent to the backend Lambda function as is. API Gateway maps the entire client request to the input event parameter of the backend Lambda function. The Lambda function's output, including status code, headers, and body, is returned to the client as is. For many use cases, this is the preferred integration type.
With a Lambda proxy integration, API Gateway requires the backend Lambda function to return output according to the following JSON format:
{
"statusCode": "...",
"headers": {
"custom-header": "..."
},
"body": "...",
"isBase64Encoded": "..."
}
where:
- statusCode = a valid HTTP status code
- custom-header = custom headers that you want to be included in the response
- body = body of the response as a JSON string
- isBase64Encoded = true/false whether binary support should be included
Image below shows the behaivour of a serverless endpoint implemented using Lambda Proxy Integration. Entire client request (including headers, body and parameters) is mapped to the event object send to the function. Our lambda function returns the object that it receives in the request:
Template format for this kind of integration is very simple. The entire configuration can be done by using just a single resource:
Resources:
EchoLpiApi:
Type: AWS::Serverless::Function
Properties:
FunctionName: echo-lpi
CodeUri: EchoFunctionJS
Handler: app.lambdaHandler
Runtime: nodejs10.x
Events:
Request:
Type: Api
Properties:
Path: /echo
Method: post
Template file: template-lpi-catch-all.yaml
This method is applicable when you wish to use an API Gateway as a pure proxy, with little to no intervention on the incoming request. The business logic of the exposed API endpoint is comprised entirely within the Lambda function, which is wholly responsible for handling and responding to the request When the backend web server opens more resources for public access, the client can use these new resources with the same API setup. But this requiers that the service developer to communicate clearly to the client developer which are the new accesible resources, and the supported operations.
API Gateway is passing the "httpMethod" action to the lambda function:
The sam AWS template configuration has to mention to the resource path {proxy+} and to include the catch-all ANY verb for the HTTP method.
Resources:
EchoLpiApi:
Type: AWS::Serverless::Function
Properties:
FunctionName: echo-lpi-catch-all
CodeUri: EchoFunctionJS
Handler: app.lambdaHandler
Runtime: nodejs10.x
Events:
ProxyApiGreedy:
Type: Api
Properties:
Path: /echo/{proxy+}
Method: ANY
Template file: template-lpi-validate.yaml
Very often, instead of writing your validation code into the lambda function it is preferable to let the API Gateway manages that for you. You can configure API Gateway to perform basic validation of an API request before proceeding with the integration request. When the validation fails, API Gateway immediately fails the request, returns a 400 error response to the caller.
Figure below shows the results of the validation performed on a incomplete body request:
API Gateway can validate both:
- request parameters
- request payload
SAM AWS template offers support to enable validation for lambda proxy integration by using "Request Model" or "Request Parameter" properties of the object describing an event source with type Api. Request model has to be configured in "Models" property of the "AWS::Serverless::API" resource:
EchoWithValidationAPI:
Type: AWS::Serverless::Api
Properties:
...
Models:
Inventor:
type: object
required:
- name
- wiki
properties:
name:
type: string
wiki:
type: string
knownFor:
type: string
EchoWithValidationFunction:
Type: AWS::Serverless::Function
Properties:
...
Events:
Request:
Type: Api
Properties:
RestApiId:
Ref: EchoWithValidationAPI
RequestModel:
Model: Inventor
Required: true
Note that, even that this example is similar with the awslabs/serverless-application-model one, there seems to be an issue with this configuration and you still need to manually activate the validator in the API Gateway console:
Template file: template-li.yaml
Lambda proxy integration are very easy to set-up, but it offers you less control over the workflow.
If you target a better separation of responsibilities, between the integration layer and functional part of your application, you should look into utilizing lambda custom integration. For example, lambda custom integration can be used to transform the request payload by using Velocity Template Language engine or to map response codes and messages the ones understood by the client application.
As shown in the figure below the API Gateway is not packaging the entire client request, rather (if no transformers are used) the body of the request is forwarded as is to the lambda function:
If you want to use custom integration, you need to use swagger to specify the interface definition:
EchoLiApi:
Type: AWS::Serverless::Api
Properties:
...
DefinitionBody:
swagger: "2.0"
paths:
/echo:
post:
responses:
"200":
description: "200 response"
x-amazon-apigateway-integration:
uri:
Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${EchoLiFunc.Arn}/invocations
responses:
default:
statusCode: 200
passthroughBehavior: when_no_match
httpMethod: POST
type: AWS
x-amazon-apigateway-any-method:
produces: application/json
EchoLiFunc:
Type: AWS::Serverless::Function
Properties:
...
Events:
Request:
Type: Api
Properties:
RestApiId: !Ref EchoLiApi
Path: /echo
Method: post
Template file: template-li-validate.yaml
In case of custom integration the entire validation of the request should happen within the swagger definition:
Resources:
EchoLiApi:
Type: AWS::Serverless::Api
Properties:
Name: echo-li-validate
StageName: Prod
TracingEnabled: True
DefinitionBody:
swagger: "2.0"
info:
title: "EchoAPI"
x-amazon-apigateway-request-validators:
Validate Body:
validateRequestBody: True
validateRequestParameters: False
paths:
/echo:
post:
consumes:
- "application/json"
produces:
- "application/json"
x-amazon-apigateway-request-validator: "Validate Body"
parameters:
- in: "body"
name: "Inventor"
required: true
schema:
$ref: "#/definitions/Inventor"
responses:
"200":
description: "200 response"
x-amazon-apigateway-integration:
uri:
Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${EchoLiFunc.Arn}/invocations
responses:
default:
statusCode: 200
passthroughBehavior: when_no_match
httpMethod: POST
type: AWS
x-amazon-apigateway-any-method:
produces: application/json
definitions:
Inventor:
description: "Inventor model"
type: "object"
required:
- "name"
- "wiki"
properties:
name:
type: "string"
wiki:
type: "string"
knownFor:
type: "string"