Introduction to CloudFormation for API Gateway

AWS API Gateway and AWS Lambda are part of the Serverless Architecture paradigm shift. The learning curve is steep and for this reason Amazon has a step-by-step tutorial on how to get started. This blog post aims to outline the required AWS resources for a similar project, but this time using AWS CloudFormation instead of the AWS Console for configuration. The sample code is deliberately simplistic, nevertheless I suggest that you start with the AWS tutorial if you have not worked with API Gateway before. If you plan to use this setup in production, I recommend that you study the provided reference documentation carefully so that you do not miss any additional configuration setting that your service may need. The source code is available in my GitHub repo.

Goal

The business logic in this example consists of Lambda function called GreetingLambda which has been configured with an appropriate execution role. If the event passed to the Lambda contains a name property, a JSON document with a greeting containing the name is returned. If not, the greeting Hello, World! is returned.

exports.handler = (event, context, callback) => {
  const name = event.name || 'World';
  const response = {greeting: `Hello, ${name}!`};
  callback(null, response);
};

When proxied by an API Gateway, it should be possible to perform a HTTP request with the name as a request parameter.

$ curl https://abc123.execute-api.eu-west-1.amazonaws.com/LATEST/greeting?name=Superman
{"greeting":"Hello, Superman!"}

CloudFormation Resources

API Gateway Rest API

First of all, we need to create an API Gateway REST API, a resource that contains a collection API Gateway resources.

"GreetingApi": {
  "Type": "AWS::ApiGateway::RestApi",
  "Properties": {
    "Name": "Greeting API",
    "Description": "API used for Greeting requests",
    "FailOnWarnings" : true
  }
}

Ref: AWS::ApiGateway::RestApi

Lambda Permission

As with all AWS resources, the API Gateway needs permission to execute the Lambda function.

"LambdaPermission": {
  "Type": "AWS::Lambda::Permission",
  "Properties": {
    "Action": "lambda:invokeFunction",
    "FunctionName": {"Fn::GetAtt": ["GreetingLambda", "Arn"]},
    "Principal": "apigateway.amazonaws.com",
    "SourceArn": {"Fn::Join": ["", 
      ["arn:aws:execute-api:", {"Ref": "AWS::Region"}, ":", {"Ref": "AWS::AccountId"}, ":", {"Ref": "GreetingApi"}, "/*"]
    ]}
  }
}

Ref: AWS::Lambda::Permission

API Gateway Stage

A stage defines the path through which an API deployment is accessible.

"GreetingApiStage": {
  "DependsOn" : ["ApiGatewayAccount"],
  "Type": "AWS::ApiGateway::Stage",
  "Properties": {
    "DeploymentId": {"Ref": "ApiDeployment"},
    "MethodSettings": [{
      "DataTraceEnabled": true,
      "HttpMethod": "*",
      "LoggingLevel": "INFO",
      "ResourcePath": "/*"
    }],
    "RestApiId": {"Ref": "GreetingApi"},
    "StageName": "LATEST"
  }
}

Here, CloudWatch logging has been enabled in the MethodSettings configuration. I choose to configure StageName as LATEST to indicate that this stage will target the $LATEST version of the Lambda function (which is the default).

Ref: AWS::ApiGateway::Stage

Logging

It is not enough to just declare logging on the stage as exemplified above. In order to get any CloudWatch logs, you must also specify an IAM role that enables API Gateway to write information to CloudWatch.

"ApiGatewayCloudWatchLogsRole": {
  "Type": "AWS::IAM::Role",
  "Properties": {
    "AssumeRolePolicyDocument": {
      "Version": "2012-10-17",
      "Statement": [{
        "Effect": "Allow",
        "Principal": { "Service": ["apigateway.amazonaws.com"] },
        "Action": ["sts:AssumeRole"]
      }]
    },
    "Policies": [{
      "PolicyName": "ApiGatewayLogsPolicy",
      "PolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [{
          "Effect": "Allow",
          "Action": [
            "logs:CreateLogGroup",
            "logs:CreateLogStream",
            "logs:DescribeLogGroups",
            "logs:DescribeLogStreams",
            "logs:PutLogEvents",
            "logs:GetLogEvents",
            "logs:FilterLogEvents"
          ],
          "Resource": "*"
        }]
      }
    }]
  }
}

This role must subsequently be passed as a property to the API Gateway Account.

"ApiGatewayAccount": {
  "Type" : "AWS::ApiGateway::Account",
  "Properties" : {
    "CloudWatchRoleArn" : {"Fn::GetAtt" : ["ApiGatewayCloudWatchLogsRole", "Arn"] }
  }
}

Ref: AWS::ApiGateway::Account

API Gateway Deployment

The API Gateway Deployment resource deploys an Amazon API Gateway RestApi resource to a stage so that clients can call the API over the Internet.

"ApiDeployment": {
  "Type": "AWS::ApiGateway::Deployment",
  "DependsOn": ["GreetingRequest"],
  "Properties": {
    "RestApiId": {"Ref": "GreetingApi"},
    "StageName": "DummyStage"
  }
}

Note that I have configured of StageName as DummyStage. This is not a mistake. If you read the documentation of the StageName property you will find that:

Note This property is required by API Gateway. We recommend that you specify a name using any value (see
Examples) and that you don’t use this stage. We recommend not using this stage because it is tied to this deployment, which means you can’t delete one without deleting the other. For example, if you delete this deployment, API Gateway also deletes this stage, which you might want to keep. Instead, use the AWS::ApiGateway::Stage resource to create and associate a stage with this deployment.

Ref: AWS::ApiGateway::Deployment

API Gateway Resource

Now that we have all the internal bits and pieces setup for the API Gateway, we can start working on the public HTTP parts of the API. In this simplified example, there will only be one endpoint, namely the /greeting resource:

"GreetingResource": {
  "Type": "AWS::ApiGateway::Resource",
  "Properties": {
    "RestApiId": {"Ref": "GreetingApi"},
    "ParentId": {"Fn::GetAtt": ["GreetingApi", "RootResourceId"]},
    "PathPart": "greeting"
  }
}

The properties are pretty straight forward, we tie the resource to a REST API, we choose a specific parent resource (in this example it is placed directly under the API root) and we define the path part name.

Ref: AWS::ApiGateway::Resource

API Gateway Method

Defining just a resource is not enough, one needs to specify one or more HTTP methods associated with the resource. This turns out to be a verbose exercise and there are more properties available than stated below, see the reference docs for details.

"GreetingRequest": {
  "DependsOn": "LambdaPermission",
  "Type": "AWS::ApiGateway::Method",
  "Properties": {
    "AuthorizationType": "NONE",
    "HttpMethod": "GET",
    "Integration": {
      "Type": "AWS",
      "IntegrationHttpMethod": "POST",
      "Uri": {"Fn::Join" : ["", 
        ["arn:aws:apigateway:", {"Ref": "AWS::Region"}, ":lambda:path/2015-03-31/functions/", {"Fn::GetAtt": ["GreetingLambda", "Arn"]}, "/invocations"]
      ]},
      "IntegrationResponses": [{
        "StatusCode": 200
      }],
      "RequestTemplates": {
        "application/json": {"Fn::Join" : ["", [
          "{",
            "\"name\": \"$input.params('name')\"",
          "}"
        ]]}
      }
    },
    "RequestParameters": {
      "method.request.querystring.name": false
    },
    "ResourceId": {"Ref": "GreetingResource"},
    "RestApiId": {"Ref": "GreetingApi"},
    "MethodResponses": [{
      "StatusCode": 200
    }]
  }
}

There is a lot of things going on:

  • The HttpMethod tells us that the client will use a HTTP GET request to call the resource.
  • The Type and Uri integration properties has been configured to target a Lambda function.
  • The StatusCode(s) listed in the IntegrationResponses are mapped to the corresponding StatusCode in the MethodResponses (together with other properties that have been omitted in this example).
  • RequestParameters declares the name and type of any request parameters. In this example, an optional query string called name is defined.
  • RequestTemplates is used to convert the incoming request to the format that the Lambda function expects. The value of the name query string just mentioned is converted to a JSON document such as {"name": "Superman"} that is passed to the Lambda function.

For more information about request and response mappings, see API Gateway API Request and Response Parameter-Mapping Reference and API Gateway API Request and Response Payload-Mapping Template Reference respectively.

Ref: API::Gateway::Method

Test

When an API Gateway is deployed, it gets a root url of the https://[api-id].execute-api.[region].amazonaws.com format. If you create a stack that contains all the CloudFormation resources mentioned above (see the cloudforation.template file in the GitHub sample repo), you can verify that the API Gateway works by issuing a GET request to an endpoint made up of the stage name and resource name appended to the root url (make sure that you use the api-id and region used by your API Gateway, check the Stack Outputs).

$ curl https://abc123.execute-api.eu-west-1.amazonaws.com/LATEST/greeting
{"greeting":"Hello, World!"}

You can also provide a request parameter:

$ curl https://abc123.execute-api.eu-west-1.amazonaws.com/LATEST/greeting?name=Superman
{"greeting":"Hello, Superman!"}

Considerations

  • An AWS generated domain name may be ok for some applications, but other times a custom domain name is preferred. For the latter case, you may find the article Use a Custom Domain Name in API Gateway interesting. For CloudFormation configuration you can then look into how a AWS::ApiGateway::BasePathMapping resource can be configured.
  • An API Gateway Stage corresponds to a version of the API in service. After it has been deployed, it is not possible to modify it. If you find the need for updates, e.g. if you would like to change the request template, you must either deploy a new stage or delete the old stage first in order for the new request mapping to be applied to any request from the clients. This limitation does not apply to Lambda implementation code which can be updated independently of the API Gateway Stage.

Update

On the November 18th, 2016 AWS introduced the Serverless Application Model (or SAM for short) that provides an alternative solution to the one described in this blog post. Please read the AWS blog post and study the related project at the AWS Labs GitHub account for more information.

Mattias Severson

Mattias is a senior software engineer specialized in backend architecture and development with experience of cloud based applications and scalable solutions. He is a clean code proponent who appreciates Agile methodologies and pragmatic Test Driven Development. Mattias has experience from many different environments, including everything between big international projects that last for years and solo, single day jobs. He is open-minded and curious about new technologies. Mattias believes in continuous improvement on a personal level as well as in the projects that he is working on. Additionally, Mattias is a frequent speaker at user groups, companies and conferences.

This Post Has 28 Comments

  1. Sri Harsha

    Thanks very much Mattias. This was very helpful.

  2. Asim

    Thank you so much for the methodical, detailed, and reproducible guide. I would not have figured out how to set up API Gateway with my working Lambda functions using CloudFormation.

    1. Mattias Severson

      @Asim: Thank you. As noted in the update, this blog post is still valid, but you will find that it will be easier to understand and less verbose if you implement the solution using SAM.

  3. Jose

    what about usage plan?
    Did you add any usage plan to your example?

  4. doctorb

    Thanks very much for this post! AWS docs still lacking good, clean examples like this one!

  5. q

    Wow. Thank you very much for this comprehensive and *working* example.

    It’s quite difficult trying to assemble these many pieces from AWS docs (“String String String”) and random blog/SO posts.

  6. Prashant

    Great stuff Mattias! Its very difficult to create working cloud formation script just following aws documentation. You have to do trial and error. Thanks for well written and working sample!

  7. Jake Scott

    This blog post has been really really helpful. Thanks heaps!

  8. Mark Cinco

    Hi! Very informative and helpful blog post.
    Just a quick question. Do I have to make a “Lambda Permission” for every “Lambda Function” than I will use in my API Gateway?

    1. Mattias Severson

      @Mark: Short answer, yes. Copied from the AWS::Lambda::Permission documentation:

      The function policy grants a specific AWS service or application permission to invoke the function.

      Thus, without it, the API Gateway is not allowed to call your function. That said, I find that SAM (or the AWS Serverless Application Model) handles this in much more concise way, since the Lambda permission it is handled implicitly by the Events part of the AWS::Serverless::Function. For example, take a look at the App Example in the SAM blog post announcement.

  9. Fayaz

    Excellent article. Very beginner friendly steps.

    1. Mattias Severson

      @Fayaz: Thanks. Please also see the comment about SAM that I made as it simplifies the CloudFormation template significantly. It was not announced when I wrote this blog post so I could not use it back then.

  10. Ashish Gaude

    Hi, its very informative, i ran your example in aws cloudformation stack, but now i want to nest api-gateway resources under resource like:
    api
    /resource-head
    /resource-1
    -GET/POST/OPTIONS
    /resource-2
    -GET/POST/OPTIONS

    how do i achieve this scenario?

    1. Mattias Severson

      Hi Ashish,

      When I wrote the blog post I used vanilla CloudFormation configuration. Did you see my comment about AWS SAM? It was introduced some time ago and it reduces the configuration significantly. Using SAM you can declare an API endpoint as an event source for your Lambda function.

  11. Nandhini Priya

    Great article! I have a question, is it possible to have lambda and api gateway in 2 different cloudformation files? If so, how do we refer Lambda function name in API gateway where we give intergration URI?

    1. Mattias Severson

      @Nandhini: Thanks. It is not unusual that you create two (or more) CloudFormation templates that have a dependency between them. One way of providing references between them is to export the ARN of a resource (or sometimes its logic name, depending on what you need) from one stack using Outputs in one stack and then provide them as input to the other stack using Parameters. Another way of doing this is to use the Fn::ImportValue. For the latter solution, you can also take a look at Walkthrough: Refer to Resource Outputs in Another AWS CloudFormation Stack in the AWS documentation.

  12. Narendra Singh

    Thanks a lot for this nice post. It saved my day.

    1. Mattias Severson

      @Narendra: Thank you. Please also spend some time to get familiar with AWS SAM (Serverless Application Model) that was introduced after I wrote this blog post. It simplifies and reduces the amount of CloudFormation configuration that you need to write manually for your Lambda / API Gateway application.

  13. Sarvothaman Vittal

    Thanks for the detailed info, it helped me enable logging in API Gateway via cloud formation. Is there also way to apply Tags to APIGateway stage via cloud formation. If so please provide the details.

    1. Mattias Severson

      @Sarvothaman: Not that I know. The AWS::ApiGateway::Stage resource does not currently (2018-05-09) have any tags property.
      If you are using AWS SAM, you will find that the AWS::Serverless::Function does indeed have a Tags property, but AFAIK those tags are applied to the resulting Lambda function and not the API Gateway stages.

  14. Maneesh Bhunwal

    Hi Mattias,

    Thank you very much for this wonderful post. sicne you have mentioned about AWS::Serverless::Function in almost all the comments, can you pelase put it in the starting of this blog, so that user can follow that directly instead of trying these steps first and then endup using AWS::Serverless::Function because that simplifies lots of stuff.

  15. Jason

    Hi Matt,

    Your Article is great and has every detailed step in it. I created an alias for lambda function called “Dev” but when when my cloudformation completes the stack setup. The only Version that is authorized to call AWS Lambda is $LATEST. How can i authorize it to API Gateway to Invoke my “Dev” Alias of Lambda function.?

    “lambdaAlias” : {
    “Type” : “AWS::Lambda::Alias”,
    “Properties” : {
    “FunctionName” : {“Fn::GetAtt”: [“getfeed”, “Arn”]},
    “FunctionVersion” : “$LATEST”,
    “Name” : “dev”
    }
    },

    “LambdaPermission”: {
    “Type”: “AWS::Lambda::Permission”,
    “Properties”: {
    “Action”: “lambda:invokeFunction”,
    “FunctionName”: {“Fn::GetAtt”: [“getfeed”, “Arn”]},
    “Principal”: “apigateway.amazonaws.com”,
    “SourceArn”: {“Fn::Join”: [“”, [“arn:aws:execute-api:”, {“Ref”: “AWS::Region”}, “:”, {“Ref”: “AWS::AccountId”}, “:”, {“Ref”: “TestApi”}, “/*”]]}
    }
    },


    Jason

    1. Mattias Severson

      @Jason: It was quite some time ago since I write this blog post, but first glance suggests that you need to update your Lambda Permission to point to Lambda Alias ARN, e.g. use "FunctionName": {"Ref": "lambdaAlias"}. Secondly, I suggest that you take a look at the AWS SAM, which simplifies the configuration, permission, lifecycle etc of AWS Lambda functions, API Gateways and other AWS resources.

  16. Amit Choudhary

    I was Googling around for content Introduction to CloudFormation for API Gateway this morning, when I came across your excellent page.

    Great stuff!!

    I just wanted to say that your page helped me a ton. I would have never found any deep insight article on Introduction to CloudFormation for API Gateway. I specially like you add flowchart of process,

    It helps to learn CloudFormation for API Gateway.

    It’s funny: I recently published top online resources to learn Aws tutorial.

    Here it is in case you’d like to check it out: https://hackr.io/tutorials/learn-amazon-web-services-aws

    Also, my tutorial might make a nice addition for learning Aws tutorial.

    Either way, thanks. And have a great day!

  17. Naga

    Hi Mattias,

    Thank you very much for this wonderful post. You have answered referencing lambda function from a different template file assuming that they are in two different stacks. How do i refer a lambda function from a separate api gateway template file and they both in the same stack (so we cannot use export and import solution)? Here is the scenario, I am planning to have one api gateway which intern integrates with multiple lambda functions. And i wanted have one stack per environment (One stack for development, one for testing, one for production) that is why i wanted to have api gateway and lambda functions all in one stack but in different template files.

Leave a Reply