Terraforming AWS API Gateway v2 with VPC Link Integration

Overview

We recently switched a client from an AWS API Gateway v1 to an HTTP v2 API. In API Gateway v1, each route (path and method) must be declared regardless of whether if it is or isn’t proxying to the same route to the backend. API Gateway v2 introduced a default route where the request is simply proxying to the backend API where the routing is handled. The original instance was setup and configured before v2 was available and served their needs at the time but as the API grew, each additional route felt like a burden.

Regardless of cloud environment, we recommend using infrastructure-as-code to create and configure resources. When available our preferred tool is Terraform for resource creation but ARM templates in Azure or CloudFormation templates in AWS work well and are better than relying on the cloud provider’s console. Given the excellent Terraform support in AWS, we relied on this tool to create the new infrastructure.

Resources

We’ll discuss each resource required below. Just a couple notes before proceeding. Each resource contains apigatewayv2 in the name. There are v1 equivalents of most of the resources so be sure to use the correct resource type. Also, there’s no naming convention used in these sample resources, which we don’t recommend in any environment. You should choose one and use it to name your resources in a consistent manner. Lastly, for the most up-to-date documentation, refer to the HashiCorp AWS provider documentation.

API Gateway v2

resource "aws_apigatewayv2_api" "api" {
  name          = "development-api-gatewayv2"
  protocol_type = "HTTP"

  tags = var.common_tags
}

This is the first resource needed, the an HTTP API Gateway v2 instance. At the time of writing, WEBSOCKET is the only other a supported protocol.

resource "aws_apigatewayv2_vpc_link" "vpc_link" {
  name               = "development-vpclink"
  security_group_ids = [module.vpclink_sg.this_security_group_id]
  subnet_ids         = module.vpc.private_subnets

  tags = var.common_tags
}

We consider using a VPC Link a good practice because this prevents direct exposure of your backend server to the public Internet by forcing all requests through the API Gateway where features like monitoring and throttling can be applied in a single location. In this example, we’re using a security group and private subnets that have been created using great AWS Terraform modules.

API Integration

resource "aws_apigatewayv2_integration" "api_integration" {
  api_id             = aws_apigatewayv2_api.api.id
  integration_type   = "HTTP_PROXY"
  connection_id      = aws_apigatewayv2_vpc_link.vpc_link.id
  connection_type    = "VPC_LINK"
  description        = "VPC integration"
  integration_method = "ANY"
  integration_uri    = "arn:aws:elasticloadbalancing:us-east-2:123456789012:listener/app/my-load-balancer/50dc6c495c0c9188/0467ef3c8400ae65"
}

Next, a private integration resource is needed. The values needed here are fairly specific for this type of integration. The integration_uri points to a load balancer and can be obtained from an existing Terraform resource that may be created in the same configuration. If not, a data element would work well to get this value. Either way, hard coding it should be avoided and is done here only to show the structure of the URI. Note that this is not a URL but is an AWS ARN.

Route

resource "aws_apigatewayv2_route" "default_route" {
  api_id    = aws_apigatewayv2_api.api.id
  route_key = "$default"
  target    = "integrations/${aws_apigatewayv2_integration.api_integration.id}"
}

If you’ve made it this far, thanks.

Nearly the last resource to create is a route. As mentioned above, we wanted this API Gateway to forward requests to the backend API server. To accomplish this, we created a default route, which is a particular type of route in API Gateway v2 and has a particular syntax to acknowledge its type; in the resource, the route_key is set to qual to $default. Once you’ve done this, all requests will be handled by your API server. The target also requires the value to start with integrations and is not just the ID of the API integration that was created above.

Stage

resource "aws_apigatewayv2_stage" "default_stage" {
  api_id      = aws_apigatewayv2_api.api.id
  name        = "$default"
  auto_deploy = true

  tags = var.common_tags
}

Finally, a stage was created. This also requires particular syntax to forward all requests to your backend API. Here, the stage name is $default as in the route above. While not required, auto_deploy has been turned out to automatically deploy changes. Given that these resources are created and maintained via code, it’s reasonable in our opinion to auto deploy changes as they should have been reviewed prior to committing the code and applying the Terraform.

When using the default stage, there is no additional path parameter used for requests. If you intend to have path parameters to indicate environment (e.g. /development/api), this won’t work for your use case.

Summary

Here we’ve demonstrated how to create an API Gateway v2 instance with a VPC link using Terraform. We hope that this helps you improve your own cloud deployment. If you want more information about using Terraform with AWS in your own cloud, please contact us.