Your Complete API Gateway and CORS Guide
Tags: aws, cdk, cloudnative, serverless
Published: 2021-04-20 12:29:35

I recently spent some quality time with some colleagues who were implementing a web app using [API Gateway](https://aws.amazon.com/api-gateway/). It's really a very useful service, but navigating it can be challenging due to the sheer number of options available. We wound up grinding out a solution and I wanted to share my learnings, not just the "how" but also the "why" in hopes it'll help others come to some of these decisions more easily.

I will probably make this part of a series, but for this article, I want to go into the ways to enable [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) or CORS in API Gateway. It's not as intuitive as it could be and depending on the sort of integration you want to use, you'll need a different implementation.

[AWS CDK](https://aws.amazon.com/cdk/) is my infrastructure-as-code tool of choice. I'll provided examples in TypeScript,  but also show some console screenshots and walk through some generated CloudFormation. Examples are [available here](https://github.com/elthrasher/apigw-cors).

## Table of Contents
* <a href="#apigateway">API Gateway</a>
* <a href="#cors">CORS</a>
* <a href="#httpapi-vs-restapi">HTTP API vs REST API</a>
* <a href="#custom-integration-with-restapi">Custom Integration with REST API</a>
* <a href="#proxy-integration-with-restapi">Proxy Integration with REST API</a>
* <a href="#proxy-integration-with-httpapi">Proxy Integration with HTTP API</a>
* <a href="#restapi-service-integrations">REST API Service Integrations</a>
* <a href="#httpapi-service-integrations">HTTP API Service Integrations</a>
* <a href="#conclusion">Conclusion</a>

## API Gateway<a name="apigateway" />

If you're in the AWS ecosystem, you have several choices for granting external users access to your application. API Gateway is a full-featured serverless option. It is often compared to [Application Load Balancer](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/). ALB has fewer features and is not serverless, but may be cheaper for high-throughput applications. There are some good resources out there that go into this topic at depth, so I won't.

API Gateway is often used for invoking Lambda functions, but can be connected to many other AWS services as well as HTTP integrations. API Gateway offers support for request validation, throttling, transformation and various authorization mechanisms. Taking full advantage of API Gateway can do a lot to offset the higher price point but there can be a high cognitive load in doing so.

## CORS<a name="cors" />

CORS is a security mechanism supported by all major web browsers. If you are dealing with web apps, you are going to contend with CORS one way or another. I have worked in environments where the CORS headers were added by some kind of ingress gateway and I've worked in environments where the headers had to be set explicitly by the applications. API Gateway supports both models, as we shall see.

I urge you to read up on best practices and make the correct choices for your application. Some of my examples use wildcards (`*`) for allowed domains. You'll typically want something more restrictive than that in a web application.

In brief, what we're going to need to do to support CORS is to add an [HTTP OPTIONS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS) method for each of our route and then set the `Access-Control-Allow-Origin` header on our responses. There are other CORS headers that can be optionally set, such as `Access-Control-Allow-Credentials`, `Access-Control-Allow-Headers`, `Access-Control-Allow-Methods` and `Access-Control-Max-Age`. Setting these headers works in exactly the same way, so I'll just focus on `Access-Control-Allow-Origin`.

## HTTP API vs REST API<a name="httpapi-vs-restapi" />

Before we can begin any API Gateway implementation, we need to decide which API implementation we're using. AWS API Gateway first launched in 2015 and has gained features steadily since, as AWS services tend to do. In 2019, AWS announced [HTTP API](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html) as a lower-cost alternative to original, which became known as [REST API](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html).

Just to editorialize for a moment, while it's not hard to keep them straight, these are bad names, since both implementations of the service use HTTP and REST and as such, the names are not at all differentiating. Oh well, never mind, AWS is [known for having this problem](https://twitter.com/QuinnyPig/status/1070451608050315264).

To add further confusion, HTTP API isn't Version 2 of API Gateway, but there *is* a version 2 of the spec. Payload format version 2.0 was initially rolled out for [WebSocket APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api.html) and is now available for HTTP API but not REST API. If you want to understand the differences, [the official docs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html) do a good job of giving us a side-by-side comparison. It's clear the intent of version 2.0 is to provide a simpler format, but you should be aware of the differences if you are used to payload version 1.0

So the three API implementations provided by API Gateway are REST API (payload format 1.0), HTTP API (choose either) and Websocket API (payload format 2.0). I won't provide any information on CORS headers for WebSocket API as it isn't part of the [WebSocket spec](https://stackoverflow.com/a/37837709/1487358).

The [official documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html) explains the feature differences between HTTP API and REST API and goes well beyond the scope of this article. I will mention that HTTP API is cheaper than REST API and thus generally the guidance is to choose it if it has all the features you need.

## Proxy Integration with REST API<a name="proxy-integration-with-restapi" />

What, you thought you'd exhausted the decision tree? We're just getting started. There's a [huge section just on the different integration types for REST API](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-integration-types.html) alone. Here we're going to be dealing with just one of those integration patterns, the [proxy integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html).

Most of the API Gateway/Lambda stacks I've seen have used proxy integrations. In this integration, we pass the request object to the function handler and construct a response object in the function as well, which is then passed back via API Gateway. That doesn't mean we're talking only about lambdaliths here! In fact, in [Matt Coulter's](https://twitter.com/NIDeveloper) [excellent article on common Lambda patterns](https://dev.to/cdkpatterns/learn-the-3-aws-lambda-states-today-the-single-purpose-function-the-fat-lambda-and-the-lambda-lith-361j), he implements all three patterns using proxy integrations.

To enable CORS in a proxy integration, we need to do two things:
1. Return a response to an OPTIONS request for each route we want to enable for CORS.
2. Return valid CORS headers from our Lambda function.

AWS CDK gives us a nice shortcut for setting those OPTIONS responses. We can use `defaultCorsPreflightOptions` in our RestApiProps. That might look something like this:

```typescript
    const restApi = new RestApi(this, 'ProxyCorsRestApi', {
      defaultCorsPreflightOptions: {
        allowOrigins: Cors.ALL_ORIGINS,
      },
    });
```

Setting this produces the following CloudFormation:

```yaml
  ProxyCorsRestApiOPTIONSEF3C92C2:
    Type: AWS::ApiGateway::Method
    Properties:
      HttpMethod: OPTIONS
      ResourceId:
        Fn::GetAtt:
          - ProxyCorsRestApiB964F16B
          - RootResourceId
      RestApiId:
        Ref: ProxyCorsRestApiB964F16B
      AuthorizationType: NONE
      Integration:
        IntegrationResponses:
          - ResponseParameters:
              method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'"
              method.response.header.Access-Control-Allow-Origin: "'*'"
              method.response.header.Access-Control-Allow-Methods: "'OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD'"
            StatusCode: '204'
        RequestTemplates:
          application/json: '{ statusCode: 200 }'
        Type: MOCK
      MethodResponses:
        - ResponseParameters:
            method.response.header.Access-Control-Allow-Headers: true
            method.response.header.Access-Control-Allow-Origin: true
            method.response.header.Access-Control-Allow-Methods: true
          StatusCode: '204'
```

And in a nutshell, this is why I like CDK! [AWS SAM also lets you cut down on your yaml quite a bit](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-api-corsconfiguration.html). I definitely prefer one of these options over raw CloudFormation, but any of them should do the job.

We also need to create an integration. The CDK for that is quite nice:

```typescript
    const proxyIntegration = new LambdaIntegration(proxyFn);

    restApi.root.addMethod('get', proxyIntegration);
```

This will produce CloudFormation:

```yaml
  ProxyCorsRestApiget33C342A8:
    Type: AWS::ApiGateway::Method
    Properties:
      HttpMethod: GET
      ResourceId:
        Fn::GetAtt:
          - ProxyCorsRestApiB964F16B
          - RootResourceId
      RestApiId:
        Ref: ProxyCorsRestApiB964F16B
      AuthorizationType: NONE
      Integration:
        IntegrationHttpMethod: POST
        Type: AWS_PROXY
        Uri:
          Fn::Join:
            - ''
            - - 'arn:'
              - Ref: AWS::Partition
              - :apigateway:us-east-1:lambda:path/2015-03-31/functions/
              - Fn::GetAtt:
                  - RestProxyFn04E83FA9
                  - Arn
              - /invocations
```

This is verbose in comparison to the CDK, but it helps us understand what exactly is happening here. In order to invoke the Lambda service, API Gateway is making an HTTP POST on the appropriate `/invocations` endpoint. Good to know, but I'd definitely take a [SAM template](https://docs.aws.amazon.com/lambda/latest/dg/services-apigateway-template.html) over this if forced into yaml.

Setting the headers is done in the actual Lambda function. Here's a simple TypeScript example:

```typescript
import { APIGatewayProxyResult } from 'aws-lambda';

export const handler = async (): Promise<APIGatewayProxyResult> => {
  return {
    body: JSON.stringify({ state: 'ok' }),
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
    statusCode: 200,
  };
};
```

Pretty straightforward in that we just return an object with the headers set, but it must be done in [this exact format](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-output-format) in order for API Gateway to map the response correctly - mapping still occurs in a proxy integration, you just don't have direct control over it.

Let's give our integrated route a look in the console.

![CORS-enabled route](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/phjn2191toi8wyvj8kyc.png)

Notice the "Integration Response" section is grayed out. Most of the other areas here don't have a lot going on. This integration pattern puts most of the work on Lambda and remains quite simple here in API Gateway.

Let's see if we can make that OPTIONS request the web browser will need. OPTIONS requests typically return an [HTTP 204](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204) response if successful.

```bash
% curl -i -X OPTIONS https://gzpd0b4wle.execute-api.us-east-1.amazonaws.com/prod/
HTTP/2 204
date: Tue, 20 Apr 2021 12:11:46 GMT
x-amzn-requestid: f409a183-4bed-4f3c-8a2b-d26b77665a99
access-control-allow-origin: *
access-control-allow-headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent
x-amz-apigw-id: eFO4aEnZIAMFkYw=
access-control-allow-methods: OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD
x-cache: Miss from cloudfront
via: 1.1 b051e9c33308597b659c33b8999b521d.cloudfront.net (CloudFront)
x-amz-cf-pop: IAD89-C2
x-amz-cf-id: ubVksQvwI3Xd7O4cli00MKp6QMnVpu6ofh2aaS7Ui7Mc3fAx-7Oaqw==
```

And then call the GET endpoint to verify the header is present.

```bash
% curl -i https://gzpd0b4wle.execute-api.us-east-1.amazonaws.com/prod/
HTTP/2 200
content-type: application/json
content-length: 14
date: Tue, 20 Apr 2021 12:12:16 GMT
x-amzn-requestid: 8335bb7d-bfd0-4814-9b16-cbbef3fad49f
access-control-allow-origin: *
x-amz-apigw-id: eFO88GedIAMFcMw=
x-amzn-trace-id: Root=1-607ec51f-791687c52f7630d700b81a35;Sampled=0
x-cache: Miss from cloudfront
via: 1.1 27eb501c8caff149895f88cac34554af.cloudfront.net (CloudFront)
x-amz-cf-pop: IAD89-C2
x-amz-cf-id: b-o4OnglA0hpW8ij-dGCGU1wnZjUyY6W43hJ2IDs46fZJjuQJ3r4kw==

{"state":"ok"}
```

So it's relatively easy to set up CORS for this pattern, but the downside is you have to add the headers to each function, which could produce a lot of boilerplate in larger stacks. I would definitely seek to add some kind of [middleware solution](https://github.com/middyjs/middy/tree/main/packages/http-cors) when adding CORS headers across many functions.

## Custom Integration with REST API<a name="custom-integration-with-restapi" />

The next pattern is a bit more complicated.

A [custom integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-custom-integrations.html) abstracts the HTTP request and response away from our Lambda function. We will have to map any parts of the request we want our function to see and then we'll have to map its response to something API Gateway can send back to the client. This pattern maximizes the work for API Gateway to do and minimizes how much we do in Lambda. Mappings are done using [Velocity Template Language (VTL)](https://docs.aws.amazon.com/apigateway/latest/developerguide/rest-api-data-transformations.html).

Why choose this integration pattern? [VTL](https://velocity.apache.org/engine/devel/vtl-reference.html) isn't exactly developer-friendly, but it's powerful. Additionally, API Gateway bills per request while Lambda bills per millisecond spent in startup and execution. If you have transformations that take meaningful amounts of time on a high-throughput API, using a custom integration could save you some money.

The other reason to think about a custom integration is it opens the door to non-Lambda integrations that use the same templates. Consider an API Gateway that serves some requests to Lambda, because some compute layer is needed for them, and integrates other requests directly with DynamoDB or S3 because they are simple enough to skip the compute layer. Such an integration would need to use mapping templates for the DynamoDB or S3 integrations already, so why not use them for Lambda as well?

Perhaps that's an edge case, but it's worth considering how best to shave your AWS bill. Service integrations and VTL could mean major savings, but only if the pattern doesn't increase development complexity by too much.

To add CORS to a custom integration we will need three things:
1. Return a response to an OPTIONS request for each route as before.
2. Add the desired headers to our integration responses.
3. Map the desired headers from our integration responses into our method responses.

The first part works just the same as it does in proxy integrations:

```typescript
    const restApi = new RestApi(this, 'CustomCorsRestApi', {
      defaultCorsPreflightOptions: {
        allowOrigins: Cors.ALL_ORIGINS,
      },
    });
```

Now we need to add the header to our integration response which means it's going to be in the CDK code.

```typescript
    const customIntegration = new LambdaIntegration(customFn, {
      integrationResponses: [
        {
          responseParameters: {
            'method.response.header.Access-Control-Allow-Origin': "'*'",
          },
          responseTemplates: {
            'application/json': JSON.stringify({
              message: '$util.parseJson($input.body)',
              state: 'ok',
            }),
          },
          statusCode: '200',
        },
      ],
      passthroughBehavior: PassthroughBehavior.NEVER,
      proxy: false,
      requestTemplates: {
        'application/json': JSON.stringify({
          input: 'this is the input',
        }),
      },
    });
```

This code contains a pair of request/response templates, which is one way to do custom integrations. The important part here is the `responseParameter` which will set the `Access-Control-Allow-Origin` header to a wildcard value. Note also I need to set `proxy: false`. CDK considers a proxy integration to be the default integration.

The third thing we'll need is to set `responseParameters` in the method's `methodResponses`.

```typescript
    restApi.root.addMethod('get', customIntegration, {
      methodResponses: [
        {
          statusCode: '200',
          responseParameters: {
            'method.response.header.Access-Control-Allow-Origin': true,
          },
        },
      ],
    });
```

The boolean value here is only whether or not the parameter is required and this would still work with a value of `false` but it will not work if this mapping isn't in place. Note that for the sake of brevity, I've only mapped a 200 OK response. In a real application, we'd need to handle error codes in both the request and response templates and mappings. If we fail to do this, we're likely to get an error directly from the API Gateway service that will be challenging to debug.

Now that this code is in place, we'll see the OPTIONS request in our CloudFormation template, as with the proxy integration and we'll also be able to see the mappings in the GET method we've added:

```yaml
  CustomCorsRestApiget57AF1EA7:
    Type: AWS::ApiGateway::Method
    Properties:
      HttpMethod: GET
      ResourceId:
        Fn::GetAtt:
          - CustomCorsRestApi3D2B9919
          - RootResourceId
      RestApiId:
        Ref: CustomCorsRestApi3D2B9919
      AuthorizationType: NONE
      Integration:
        IntegrationHttpMethod: POST
        IntegrationResponses:
          - ResponseParameters:
              method.response.header.Access-Control-Allow-Origin: "'*'"
            ResponseTemplates:
              application/json: '{"message":{"output":"$util.parseJson($input.body)"},"state":"ok"}'
            StatusCode: "200"
        PassthroughBehavior: NEVER
        RequestTemplates:
          application/json: '{"input":"this is the input"}'
        Type: AWS
        Uri:
          Fn::Join:
            - ""
            - - "arn:"
              - Ref: AWS::Partition
              - :apigateway:us-east-1:lambda:path/2015-03-31/functions/
              - Fn::GetAtt:
                  - CustomFnFE03B841
                  - Arn
              - /invocations
      MethodResponses:
        - ResponseParameters:
            method.response.header.Access-Control-Allow-Origin: true
          StatusCode: "200"
```

The upside of our custom integration is our function can basically be anything we like. We aren't bound by any particular API, but we do need to map to our templates. Here's my sample function:

```typescript
interface inputEvent {
  input: string;
}

export const handler = async (event: inputEvent): Promise<string> => {
  return event.input;
};
```

I defined an interface that maps to my request template and returns a string which is output in the response template. There's a lot that can be done here but that will have to be the subject of other investigations.

The console view is a bit more relevant now. If we drill down into that Integration Response, we can find the header mappings setting CORS headers for as well as the request template.

![CORS Custom Integration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/86o95hwuo56bsek9ry78.png)

Let's run the same test again for OPTIONS.

```bash
% curl -i -X OPTIONS https://153hn2leyd.execute-api.us-east-1.amazonaws.com/prod/
HTTP/2 204
date: Tue, 20 Apr 2021 12:13:41 GMT
x-amzn-requestid: 4e7b8882-56d6-497a-8850-cd4b3a274968
access-control-allow-origin: *
access-control-allow-headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent
x-amz-apigw-id: eFPKaHsioAMFliA=
access-control-allow-methods: OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD
x-cache: Miss from cloudfront
via: 1.1 c84ecfd128e1f4c41a53a2b42410f3b8.cloudfront.net (CloudFront)
x-amz-cf-pop: IAD89-C3
x-amz-cf-id: 8adT0FmbEfTm3wxzoK6B5ZgM2sHoxPpML2F2wSJU-X6UUjfZCI0nDw==
```

And on the GET endpoint.

```bash
% curl -i https://153hn2leyd.execute-api.us-east-1.amazonaws.com/prod/
HTTP/2 200
content-type: application/json
content-length: 55
date: Tue, 20 Apr 2021 12:13:46 GMT
x-amzn-requestid: bf6d4d09-cf3a-4772-bb19-fb2b0119e0ec
access-control-allow-origin: *
x-amz-apigw-id: eFPLIF_ToAMFTbQ=
x-amzn-trace-id: Root=1-607ec57a-1e08f2330043a36640aa8aac;Sampled=0
x-cache: Miss from cloudfront
via: 1.1 ba82151bf51e4c722c5305c983d8b71e.cloudfront.net (CloudFront)
x-amz-cf-pop: IAD89-C3
x-amz-cf-id: dPiy6f4MhSYlvUz35vmWTFqiAWfGynZw0X_oKvrdWSWZA-nLUxWjlA==

{"message":{"output":"this is the input"},"state":"ok"}
```

The header is set correctly!

## Proxy Integration with HTTP API<a name="proxy-integration-with-httpapi" />

It's pretty obvious that one of the goals in developing HTTP API was to simplify. This is exemplified in the fact that HTTP API [only supports proxy integrations](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html) with Lambda.

That said, setting CORS for a proxy integration works differently in HTTP API than it does in REST API. Anybody transitioning from REST API to HTTP API is likely to get caught up by the change. Let's walk through the CDK code:

```typescript
    const httpApi = new HttpApi(this, 'ProxyCorsHttpApi', {
      corsPreflight: { allowMethods: [CorsHttpMethod.ANY], allowOrigins: ['*'] },
    });
```

This looks a lot like how it's done in REST API, however when we look at the generated CloudFormation, there's a sizable difference.

```yaml
  ProxyCorsHttpApiC5DA21B9:
    Type: AWS::ApiGatewayV2::Api
    Properties:
      CorsConfiguration:
        AllowMethods:
          - "*"
        AllowOrigins:
          - "*"
      Name: ProxyCorsHttpApi
      ProtocolType: HTTP
```

That's it! You do not have to manually set OPTIONS requests or map headers. Instead the API Gateway service reads the CORS configuration and manages all of this for you. If you are describing your API Gateway in CloudFormation, you're definitely going to appreciate this innovation.

Likewise the AWS Console for API Gateway has a specific section for configuring CORS.

![HTTP API CORS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/whvl3zdk99vp5ct4x0kj.png)

Just for the sake of knowledge, I tried and you can actually skip all of this and return headers from your Lambda function. That works, but then if you configure CORS in API Gateway, it'll overwrite the headers your function returns.

So let's make an OPTIONS request to our endpoint.

```bash
% curl -i -X OPTIONS https://6as8srxaw2.execute-api.us-east-1.amazonaws.com
HTTP/2 204
date: Tue, 20 Apr 2021 12:15:20 GMT
apigw-requestid: eFPZ2jtpoAMEVjQ=
```

Now for the trick. Everything is looking good, so I'll call my endpoint.

```bash
% curl -i https://6as8srxaw2.execute-api.us-east-1.amazonaws.com
HTTP/2 200
date: Tue, 20 Apr 2021 12:15:27 GMT
content-type: text/plain; charset=utf-8
content-length: 14
apigw-requestid: eFPa1hVpIAMEVRw=

{"state":"ok"}
```

No CORS header! What happened? It turns out that unlike REST API, HTTP API will only set the header if you pass in an `Origin` header on the request. Let's try that again.

```bash
% curl -i -H "Origin: https://mysite.com" https://6as8srxaw2.execute-api.us-east-1.amazonaws.com
HTTP/2 200
date: Tue, 20 Apr 2021 12:16:19 GMT
content-type: text/plain; charset=utf-8
content-length: 14
access-control-allow-origin: *
apigw-requestid: eFPjFitBIAMEVIw=

{"state":"ok"}
```

This is probably fine and correct behavior, but I'm certain it has caused stress and annoyance from developers trying to build on HTTP API. The [official docs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-cors.html) do mention CORS headers returned from Lambda will be ignored, but do not mention that the request must contain an `Origin` header for CORS to work.

## REST API Service Integrations<a name="restapi-service-integrations" />

As mentioned above, service integrations (meaning API Gateway invokes an AWS service directly without Lambda), follow the same pattern as custom integrations. You will need to take the same steps to enable CORS. Although I don't talk about CORS, I do have an [article on integrating DynamoDB with REST API](https://dev.to/elthrasher/aws-cdk-api-gateway-service-integration-with-dynamodb-2ek0). There isn't a lot of information in the [official docs](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-integration-types.html) on how to build out service integrations or even what services can be integrated with in this way, but think of it as API Gateway being configured to make SDK calls and you'll be somewhere in the vicinity of figuring this out. 

## HTTP API Service Integrations<a name="httpapi-service-integrations" />

Continuing the pattern we've seen throughout, HTTP API service integrations are simpler, but more limited. The [docs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-aws-services-reference.html) limit to just five different services you can integrate with at the time of this writing. The good news if you want to use one of these integrations and need to support CORS is you'll do the exact same thing you needed to do for an HTTP API Lambda proxy integration.

## Conclusion<a name="conclusion" />

It's not too hard to navigate any one of these solutions, but it might be hard to keep them straight and the documentation isn't always clear. It's helpful to be able to correlate the implementation with the API choices you're making. Once we have that down, we can stop the churn and appreciate the gains we get from this service.

I'm interested in covering authorization and request/response validation in future articles. Feel free to drop a comment if there's any topic you'd like to see explored at depth!

COVER: <a href="https://commons.wikimedia.org/wiki/File:Offenbach_Voyage_stereoscope_3.jpg">Published by Actualités Théâtrales J.M. (Paris) ca. 1880–1890</a>, Public domain, via Wikimedia Commons