API Gateway with AWS SAM Template

Charles Yang
carsales-dev
Published in
9 min readJun 7, 2019

Last year, I was exposed to the AWS API Gateway and played around with it in my own time. I wrote down my journey on how to set up a custom authorizer for AWS API Gateway in C#. This year, I finally got a chance to use it for a project at carsales.

In this project, we have a front-end application providing some basic CRUD functionality and a back-end communicating with multiple microservices. With all the microservices already provided by other teams, the goal for me is just to gather the endpoints I need and serve it to the front-end as a single point of contact. AWS API Gateway is the perfect service for this.

Project Architecture

As already mentioned, I will be using the Serverless Application Model (SAM) provided by AWS to set up my AWS resources. If you are aware of the other frameworks such as the Serverless Framework and are already using them, I recommend you to s̶̶̶k̶̶̶i̶̶̶p̶̶̶ ̶̶̶t̶̶̶h̶̶̶i̶̶̶s̶̶̶ ̶̶̶a̶̶̶r̶̶̶t̶̶̶i̶̶̶c̶̶̶l̶̶̶e̶̶̶.

I found out one of the sections we are going through today is not so easy using just the SAM template without any extra effort (might be easier using the Serverless Framework), however I am a stubborn kind of person. Since I have started my project with the AWS SAM template, I would also like to finish with it.

Things are getting more serious now.

Prerequisites

SAM template walkthrough

We will start with a fresh new project called basic-aws-apigateway-demo. Let’s create it by using the aws-sam-cli.

sam init --runtime python3.7 -n basic-aws-apigateway-demo

I will be using python for this project. The goal is to have a single point of contact for a front-end. This makes the start time of a lambda relatively important and I believe Python has the fastest execution time among all the lambda supported languages.

Now let’s start looking into the SAM template (template.yaml).

Resources

  • API Gateway
  • Lambda Function

First of all, we need to focus on two resources.

API Gateway

Resources: BasicAWSApiGateway

In the code above, we have created a resource type AWS::Serverless::Api BasicAWSApGateway. There are more configs we can add under the Api type, however I will keep it to a minimum for this demo.

The outputs section provides an area we can output our resources specified in this template.yaml. This comes in handy when we have other CloudFormation templates that need to access the resource specified in this template. I will show an example of this later in this post.

Lambda Function

Resource: HelloWorldFunction

Next, we added a new resource type AWS::Serverless::Function(Lambda) HelloWordFunction. More configs can be found here. Just like the API Gateway, I will keep my configuration to a minimum. The RestApiId under the properties is using a Ref function. It is referencing to the API Gateway we have just created in the same template. It basically tells the Lambda to attach to the created API Gateway.

Now we can try to deploy our basic API Gateway.

// package
sam package --template-file template.yaml --output-template-file output.yaml --s3-bucket my-deploy-bucket
// deploy to CloudFormation
sam deploy --template-file output.yaml --stack-name BasicAWSAPIGatewayDemo --capabilities CAPABILITY_IAM
CloudFormation successfully created
Resources
Outputs

When we click on the URL in our outputs panel, we should able to see the JSON response.

{
"message": "hello world"
}

Custom Authorizer

After we have successfully deployed an API Gateway, we now will be thinking of the requirement to secure some endpoints, especially the POST, PUT and DELETE methods.

This will be the best time to introduce the Custom Authorizer feature provided by AWS API Gateway. I had looked into his feature last year. This time I implemented my custom authorizer using Python instead of C#. As I mentioned earlier, the main reason for this is the function execution efficiency, quicker the start time the better. It is going to be used by an Angular web application.

I won’t go through how to create a custom authorizer here. but if you are interested in seeing how it's done, you can visit my old post. The concept is pretty much the same just written in a different language. I will be showing how we could attach it to our SAM template.

I have built my authorizer in another project with this SAM template.

Template for the custom authorizer

As we see from the output panel, I output the resource BasicAuthorizerFunction. This will allow me to reference it in the API Gateway template.

Let’s revisit our API Gateway SAM template and reference the output resource BasicCustomAuthorizer.

Add Auth to BasicAWSApiGateway

We can see a new session Auth has been added under the properties of the BasicAWSApiGateway. Let’s have a closer look into the FunctionArn. We used the ImportValue function to import the export name specified in our BasicCustomAuthorizer template. This will import the value we specify from the other CloudFormation (this case is the lambda Arn for the custom authorizer) to this template.

After deploying this change, when we hit the URL from our API Gateway output panel again, it will respond with a 401 Unauthorized.

401 Unauthorized

Let’s try again with a valid token.

Success with a valid token

CORS

After setting up our API Gateway with proper authorization, it should be ready to be consumed by our Angular front-end. For demo purposes, I am going to start a basic Angular project by using the Angular CLI and set up a very basic HTTP request in the app.component.

ng new basic-aws-apigateway-demo-app
basic app.component.ts

I start the angular project by using ng serve and I immediately see the error in the console log.

CORS nightmare — preflight request 🔥 🔥 🔥

This has always been a very frequently asked question and people can spend a lot of time try to find a solution to this question.

Now, we come to the painful part of using SAM. If we want to have everything set up properly with a single SAM template. It is impossible at this stage.

CORS in API Gateway involves several configurations. It cannot be simply done by one click even though it provides an ENABLE CORS action in its menu.

Actions menu for AWS API Gateway

Well anyway, it has never worked for me when I click on the action and process it with default settings.

default setting never works for me

Since we are using the SAM template, surely we can expect to be able to deploy to CloudFormation and have everything worked as expected without any more settings required on AWS console.

Swagger Definition

This is mentioned in the official SAM GitHub page

The swagger definition comes to the r̵e̵s̵c̵u̵e̵. In order to have a swagger definition defined in AWS API Gateway, we need to create a swagger definition (swagger.yaml) and included it in our SAM template.

Cors and DefinitonBody added to our BasicAWSApiGateway resource

We have made some minor changes to our original AWS::Serverless::Api resource. We added two new properties Corsand DefinitionBody. I have used * for our CORS setting to allow any website to use this endpoint for simplicity. If you would like to only allow a specific site, please make sure you have the quotation right. It has to be "'www.example.com'". The specified URL always needs to be wrapped with a double quote and followed by a single quote.

Inside our DefinitionBody, we have used Fn::Transform and AWS::Include to retrieve our swagger definition. The idea here is to allow our swagger definition to use the defined resources from the included template if required. You can see in the swagger definition below how is it used.

For each endpoint, we need to have at least 50 lines definition specified in our swagger.yaml

If you can have a closer look at the x-amazon-apigateway-integration under the get method, you will see the uri: is using HelloWordFunction.Arn. We could get the Arn specified in our SAM template because we used the Fn::Transform and AWS::Include in our DefinitionBodyas mentioned earlier. The beneath httpMethod will always have to be POST as well.

Since we have default authorizer define in our SAM template under the resource BasicAWSApiGateway, we need to make sure that the option endpoint needs to be excluded. We do not need the options endpoint to contain any security, hence a security property is defined under options and set it as NONE.

Wait! Don’t we want to have everything specified in our template and after deployment everything should be working as expected?

Unfortunately, the current SAM template cannot handle CORS properly. It is required to have the swagger definition. We could always add aws s3 cp swagger.yaml s3://my-deploy-bucket to our CI/CD pipeline to ease the deployment.

After we have deployed our changes, let us try to hit the endpoint again with our simple website.

CORS nightmare — Access-Control-Allow-Origin header missing 🔥 💥 🔥 💥 🔥 💥

In order to resolve this error, we need to revisit our helloworld lambda function. Let’s add the header to our response and deploy again.

We need to add Access-Control_allow-Origin header to our lambda functions
Got the response `hello world`
It works!! 👍

So are we using it?

There is no doubt on the benefits which the AWS API Gateway can bring to us. In carsales, we have many teams across the company, and each team uses it's on domain knowledge to build up their own APIs. In order to aggregate them into a single point of contact for applications to consume, it is important to put many things into consideration, especially the cost and resources.

One of the biggest benefits of AWS API Gateway compares hosting an application on EC2 in Carsales is, AWS API Gateway only gets charged when a request happens. It is perfect for small internal administration applications which do not need to be there 24/7.

The other reason is resources. It is getting easier and easier for developers to spin up applications on Cloud. Besides the SAM template, there are many others like Serverless Framework and Terraform. Quickly create a template and share among teams become critical. It could save other teams lots of time and effort.

With the current SAM template the CORS configuration involves some complexity and still need a swagger definition to make it work perfectly. Hopefully, the SAM template could introduce something like CORS: true as property under AWS::Serverless::Api in the future and allow developers to further speed up their development flow.

I have included the repository for all the projects I used in this article in references, free to check out and try it yourself.

Thanks for reading. 😃

At carsales we are always looking for fun and talented people to work with. If you liked what you read and are interesting in joining, please check out what positions we have available in our careers section.

References

--

--