Using all new Amazon API Gateway HTTP APIs with Serverless Framework

Tags: serverless, aws, tutorial
Published: 2020-03-03 00:43:15

It is easy to build and maintain large scale APIs with Amazon API Gateway.  From REST to WebSockets APIs, HTTP Proxies to workloads with request transformation, authentication and validation.

One common integration for building APIs is to proxy it to an AWS Lambda. Listening to their customers feedback, Amazon went one step further and on [last year re:Invent they released a new feature](https://aws.amazon.com/blogs/aws/aws-launches-previews-at-reinvent-2019-wednesday-december-4th/) called [HTTP APIs for Amazon API Gateway](https://aws.amazon.com/about-aws/whats-new/2019/12/amazon-api-gateway-offers-faster-cheaper-simpler-apis-using-http-apis-preview/).

HTTP APIs offer the core functionality of Amazon API Gateway and are optimised for building APIs that proxy to AWS Lambda functions or HTTP backends. With [cost savings up to 70% compared to REST APIs](https://aws.amazon.com/api-gateway/pricing/#HTTP_APIs_.28Preview.29), significant performance improvements (without the Amazon API Gateway service overhead), out of the box features like throttling, metrics, logging, OIDC, OAuth and built-in support for CORS.

# What is the difference between HTTP APIs and REST APIs in Amazon API Gateway?
As mentioned above, HTTP APIs are focused and optimised for APIs proxying request to an AWS Lambda or HTTP backends. Any other offers and features from REST APIs aren't available. I will list some features heres, but they are not exhaustive and can change, since Amazon can ship more (or less) features anytime.

**What features ARE NOT AVAILABLE while using HTTP APIs?**

* **Custom Authorizers:** AWS Lambda and IAM (You can use Amazon Cognito as a JWT issuer only)
* **Integration:** HTTP, AWS Services, Private Integration or Mocks (we can use HTTP as a proxy integration only)
* **API Management:** Usage Plans, API Keys, custom domains with TLS 1.0 or wildcard custom domains names.
* **Development:** Cache, Request transformation, Request/Response validation, Test Invocation
* **Security:** Client Certificates, AWS WAF, Resource policies
* **API Type:** Edge-optimized, Private (HTTP APIs are Regional only)
* **Monitoring:** Access logs to Amazon Kinesis Data Firehose, AWS X-Ray

While the list above can be scary and maybe you been thinking "...damn, that is too much, I'll keep using REST APIs". I dare you to think again, usually, we do not use 90% of the features on this list, but we would be paying for it if we misconfigured our projects.

With HTTP APIs, AWS helps us to [falling into the pit of success](https://blog.codinghorror.com/falling-into-the-pit-of-success/), with enhanced performance, cheaper with great features and an easier developer experience. 

This service is still in Beta, with fast improvements and moves towards General Availability. [You can stay up to date  following this AWS page](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html).

# Building your first HTTP API with Serverless Framework
At  [Serverless Guru](https://serverlessguru.com/), we love helping you to migrate, build or train your team on serverless best practices.

Last week, [our official partner Serverless](https://serverless.com/partners/), released its [support for HTTP APIs with Serverless Framework](https://serverless.com/blog/aws-http-api-support/).

We can now leverage the awesome features of Serverless Framework to build products using all new Amazon API Gateway HTTP APIs!

Let's review the following `serverless.yml` file:

```yaml
service: example-http-api-service

provider:
	name: aws

functions:
  getItems:
    handler: handler.getItems
    events:
      - httpApi: "GET /items"
```

Even with all the differences between HTTP APIs and REST APIs, Serverless Framework decided to propose a new event, `httpApi` to attach functions to HTTP APIs in your `serverless.yml` file, keeping the syntax familiar and easy to migrate.

Exploring the HTTP verbs, we have more routing options, like:

```yaml
...
functions:
  ...
  postItem:
    handler: handler.postItem
    events:
      - httpApi: "POST /items"
  getItem:
    handler: handler.getItem
    events:
      - httpApi: "GET /items/{id}"
  putItem:
    handler: handler.putItem
    events:
      - httpApi: "PUT /items/{id}"
  deleteItem:
    handler: handler.deleteItem
    events:
      - httpApi: "DELETE /items/{id}"
...
```

If we need to customise our `httpApi` event, we can use the object syntax:

```yaml
...
  getReceipt:
    handler: handler.getReceipt
    events:
      - httpApi:
          method: GET
          path: /receipt/{receiptId}
          timeout: 5
...
```

We can define a catch all route using:

```yaml
...
  catchAll:
    handler: handler.catchAll
    events:
      - httpApi:
          path: "*"
...
```

# Protecting your routes with JWT Authorizers
By default, all endpoints are publicly accessible. To protect our routes, HTTP API supports JSON Web Tokens, commonly used by OAuth 2.0 and Native OpenID workflows, called [JWT Authorizers](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-jwt-authorizer.html).

We can restrict access to our endpoints by configuring a JWT Authorizer in our `provider` section:

```yaml
provider:
  ...
  httpApi:
    authorizers:
      myCustomJwtAuthorizer:
        identitySource: $request.header.Authorization
        issuerUrl: https://...
        audience:
          - xxxx
          - xxxx
```

To authorise your request, [Amazon API Gateway uses a general workflow of 4 steps](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-jwt-authorizer.html) to validate your token by calling your JWT Authorizer. Make sure your issuer is following these requirements.

In case you wanna use Amazon Cognito we could use the following syntax:

```yaml
provider:
  ...
  httpApi:
    authorizers:
      customCognitoAuthorizer:
        identitySource: $request.header.Authorization
        issuerUrl: https://cognito-idp.${region}.amazonaws.com/${cognitoPoolId}
        audience:
          - cognitoClientIDWeb
          - cognitoClientIDMobile
```

After declaring a JWT Authorizer, we can define which endpoints we want restrict access by configuring an `authorizer` key:

```yaml
functions:
  ...
  getProfile:
    handler: handler.getProfile
    events:
      - httpApi:
          method: GET
          path: /me
          authorizer: customCognitoAuthorizer
```

If we need to extend the authorisation to include scopes to our endpoint, we can change it to:

```yaml
functions:
	...
  getProfile:
    handler: handler.getProfile
    events:
      - httpApi:
          method: GET
          path: /me
          authorizer:
            name: customCognitoAuthorizer
            scopes:
              - user.id
              - user.email
```

# Fine grain CORS control
With built-in support for CORS in HTTP API, Serverless Framework made it simple for its users by defining a `cors: true` property by default:

```yaml
provider:
  httpApi:
    cors: true
```

This default behaviour implies and ensure the following headers:

- `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`
- `Access-Control-Allow-Methods: OPTIONS` + all methods you defined in your route (`GET`, `POST` etc)

In case you need a better control over it, you can manipulate the `provider.httpApi.cors` property and define:

```yaml
provider:
  ...
  httpApi:
    ...
    cors:
      allowedOrigins:
        - https://my-allow-domain.com
        - https://allow-this-one.com
      allowedHeaders:
        - Authorization
        - Content-Type
      allowedMethods:
        - GET
        - POST
      allowCredentials: true
      exposedResponseHeaders:
        - Special-Response-Header
      maxAge: 6000 # in seconds
```

# Turning on Access Logging
Similar to REST APIs, we can turn on access logs in HTTP API by using `provider.logs.httpApi` like:

```yaml
provider:
  ...
  logs:
    ...
    httpApi: true
```

If you have a particular log format, you can use the `.format` property:

```yaml
provider:
  ...
  logs:
    ...
    httpApi:
      format: '{ "requestId":"$context.requestId", "ip": "$context.identity.sourceIp", "requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod","routeKey":"$context.routeKey", "status":"$context.status","protocol":"$context.protocol", "responseLength":"$context.responseLength" }'
```

The custom format above is used by default when you use `provider.logs.httpApi: true`, but can be used as a reference in case you wanna change it.

# All HTTP API options reference
To illustrate the possibilities with Serverless Framework, we can define the following options when using HTTP APIs:

```yaml
provider:
  httpApi:
    id: ApiID # ID of externally created HTTP API to attach resources
    timeout: 5
    cors: true
    authorizers:
      myJwtAuthorizer:
        identitySource: $request.header.Authorization
        issuerUrl: https://cognito-idp.${region}.amazonaws.com/${cognitoPoolId}
        audience:
          - xxxx
          - xxxx
  logs:
    httpApi: true

functions:
  myFunction:
    handler: myFunction.handler
    events:
      - httpApi:
          method: GET
          path: /url-path/{param}
          timeout: 5
          authorizer:
            name: myJwtAuthorizer
            scopes:
              - user.id
              - user.email
```

As we can see, the syntax is familiar and easy to use. Making the usage of HTTP APIs friendly and approachable for all projects already using Serverless Framework.

# Conclusion
HTTP APIs are designed for low-latency, cost-effective AWS Lambda proxy and HTTP proxy APIs. HTTP APIs support OIDC and OAuth 2.0 authorization, and come with built-in support for CORS.

**When should I used it?**

With something quite new, it is always a hot topic! HTTP APIs main features orbiting around:

- JWT Authorizers for JWT AuthN / AuthZ
- Proxy based API, using Lambda or HTTP backend
- Built-in CORS support

If you are already using or can support JWT token, most of your requests are being proxied to an AWS Lambda and you require CORS for your clients. **You should use or migrate to HTTP API**. With an infrastructure optimized and enhanced for these features, you will see great cost reduction with better performance.

With this new flavour in Amazon API Gateway, the enhanced performance and lower costs can be a huge win when building JWT based APIs.

The service is still [limited to a few regions](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html), but if you deploying workloads on them already, get ready to pair it up with Serverless Framework and leverage all the advantages today!

Written by Serverless Guru Eduardo Rabelo 
@oieduardorabelo
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/eufw2uqasm7pfqx8kgon.jpg)