geek pro tem

AWS API Gateway and OpenAPI Configs - Initial Thoughts

Dec 26, 2023 - 3 minute read

I’ve recently been working on building some Terraform code to manage API Gateway instances, and ran into a fair amount of confusion on making an OpenAPI spec that did what I wanted.

My use-case was to proxy to Lambda functions for the backend, which is kind of cheating as you can skip a lot of the more tedious schema definitions. I’m doing this as a template in Terraform so there is some variable interpolation here.

Here’s a small example that I’ll dissect. I’m also using YAML instead of JSON here because I value my eyeballs.

openapi: "3.0.1"
info:
  title: "${API_NAME}"
  version: "${API_VERSION}"

servers:
- url: "https://${API_HOSTNAME}"

paths:

  "/test":
    get:
      x-amazon-apigateway-integration:
        type: "HTTP_PROXY"
        httpMethod: "GET"
        uri: "https://ip-ranges.amazonaws.com/ip-ranges.json"

  "/echo":
    get:
      x-amazon-apigateway-integration:
        httpMethod: "POST"
        uri: ${INVOKE_ECHO_ARN}
        credentials: ${APIGW_ROLE}
        responses:
          default:
            statusCode: "200"
        passthroughBehavior: "when_no_match"
        contentHandling: "CONVERT_TO_TEXT"
        type: "aws_proxy"
    post:
      x-amazon-apigateway-integration:
        httpMethod: "POST"
        uri: ${INVOKE_ECHO_ARN}
        credentials: ${APIGW_ROLE}
        responses:
          default:
            statusCode: "200"
        passthroughBehavior: "when_no_match"
        contentHandling: "CONVERT_TO_TEXT"
        type: "aws_proxy"
    delete:
      x-amazon-apigateway-integration:
        httpMethod: "POST"
        uri: ${INVOKE_ECHO_ARN}
        credentials: ${APIGW_ROLE}
        responses:
          default:
            statusCode: "200"
        passthroughBehavior: "when_no_match"
        contentHandling: "CONVERT_TO_TEXT"
        type: "aws_proxy"
    delete:
      x-amazon-apigateway-integration:
        httpMethod: "POST"
        uri: ${INVOKE_ECHO_ARN}
        credentials: ${APIGW_ROLE}
        responses:
          default:
            statusCode: "200"
        passthroughBehavior: "when_no_match"
        contentHandling: "CONVERT_TO_TEXT"
        type: "aws_proxy"
    patch:
      x-amazon-apigateway-integration:
        httpMethod: "POST"
        uri: ${INVOKE_ECHO_ARN}
        credentials: ${APIGW_ROLE}
        responses:
          default:
            statusCode: "200"
        passthroughBehavior: "when_no_match"
        contentHandling: "CONVERT_TO_TEXT"
        type: "aws_proxy"
    options:
      responses:
        "200":
          description: "200 response"
          headers:
            Access-Control-Allow-Origin:
              schema:
                type: "string"
            Access-Control-Allow-Methods:
              schema:
                type: "string"
            Access-Control-Allow-Headers:
              schema:
                type: "string"
          content: {}
      x-amazon-apigateway-integration:
        responses:
          default:
            statusCode: "200"
            responseParameters:
              method.response.header.Access-Control-Allow-Methods: "'GET,HEAD,OPTIONS,POST,PUT,DELETE,PATCH'"
              method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
              method.response.header.Access-Control-Allow-Origin: "'*'"
        requestTemplates:
          application/json: "{\"statusCode\": 200}"
        passthroughBehavior: "when_no_match"
        type: "mock"

components:
  schemas:
    Empty:
      title: "Empty Schema"
      type: "object"

So that’s a lot of lines for what amounts to 2 endpoints and a handful of methods.

The first endpoint is the /test one. This is just to make sure that the config works and is a simple HTTP proxy over to the AWS IP list.

The /echo endpoint will route to a lambda that will just echo back the APIGW request. I’ve found this really helpful as a way to test to make sure that data is getting to the backend and to allow me to debug my requests.

There are the gotchas that people should be aware of…

Everything is a POST. Sort of.

Regardless of the HTTP method being sent, which is defined as the first level under /echo, the httpMethod of the integration will need to be a POST. This is what the Lambda function will require. The AWS docs do cover this, but it is unintuitive at first so I wanted to mention it here. If you use something other than POST, you’ll get a weird response back.

Quotes Matter

If you were to export an OpenAPI spec from your existing APIGW and try to use that file, you’d likely get failures. That’s because the paths, such as /echo are missing quotes! You’ll need to make sure to quote anything that starts with a /.

Use the Correct Role

Here I’m passing in the ARN of the APIGW as a variable. If you do this, it needs to be the ARN of the role that you assign to the APIGW. Seems obvious, but once you really get into the Terraform, there are many roles and ARNs for the APIGW and Lambdas and it can get overwhelming to use the right ones in the right places.

Use the Correct ARN

Similar to the above, the ARN for the target Lamba isn’t the Lambda ARN, but the invokation ARN, which isn’t the same thing.