AWS API Gateway with Terraform

Or, how a backend developer coded some infrastructure with zero DevOps knowledge

Ever wanted to create something in AWS? Feel over amazed with the AWS interface? Do you cry a little bit inside every time you look at the AWS dashboard? Then stay tuned for something that can ease your life, more than sliced bread did 🍞.

In recent times I had the opportunity of diving (and might I had, it was head first in some shallow waters) into the world of Terraform. The objective was, in a very first step, to create a proxy gateway in front of our API. At first this will simply be a “passthrough” proxy were everything that comes into to the API Gateway will be passed to our API and sent back.

How I imagine AWS looks

I can only imagine your face right now… “But that’s the same as having nothing at all..”

You’re correct but let me continue by saying that this will just be the first step to something greater.


Most of the projects start as a monolith and then, in time, comes the part where we start splitting it in the “microservices” we all love. With this change in architecture there will be a part of your code that will start to look like a router, where it receives some request from the outside world and proceeds to redirect it to whoever is responsible for answering it. The AWS API Gateway is here so, we can lift those concerns from your service. We can even add more cross-cutting concerns to it (like authentication or rate limiting). Having this approach will even help with the latency of your response as it will skip the step of interpreting code to know were it should go, because it’s doing it directly in the AWS API Gateway.

Enough chit-chat and let’s get some coding done, starting with the creation of the api entry:

resource “aws_api_gateway_rest_api” “api” {
name = “api-gateway”
description = “Proxy to handle requests to our API”
}

In here we are creating the REST API resource to where all the requests are going to hit. Next we will start to configure what we want this API to do:

resource "aws_api_gateway_resource" "resource" {
rest_api_id = "${aws_api_gateway_rest_api.api.id}"
parent_id = "${aws_api_gateway_rest_api.api.root_resource_id}"
path_part = "{proxy+}"
}
resource "aws_api_gateway_method" "method" {
rest_api_id = "${aws_api_gateway_rest_api.api.id}"
resource_id = "${aws_api_gateway_resource.resource.id}"
http_method = "ANY"
authorization = "NONE"
  request_parameters = {
"method.request.path.proxy" = true
}
}
resource "aws_api_gateway_integration" "integration" {
rest_api_id = "${aws_api_gateway_rest_api.api.id}"
resource_id = "${aws_api_gateway_resource.resource.id}"
http_method = "${aws_api_gateway_method.method.http_method}"
  integration_http_method = "ANY"
type = "HTTP_PROXY"
uri = "http://your.domain.com/{proxy}"

request_parameters = {
"integration.request.path.proxy" = "method.request.path.proxy"
}
}

Let’s do this in small steps so it’s clearer what is happening here.

Resource

This is where we’ll configure on what endpoint are we listening for requests. The path_part argument will contain a string that represents the endpoint path, as our case is a simple proxy, AWS provides a special handler to listen all the requests, the "{proxy+}". This handler can also be applied to a more specific path, i.e "users/{proxy+}" where it will listen to anything starting with users ( i.e users/1/posts , users/3/notes , etc). The other values presented in there are related to where will this resource be applied, the rest_api_id will have the id of what API we are mounting this resource and the parent_id has the id of the parent on where are mounting this. This last one can be mounted directly on the root api (as we have) or mounted in another resource making it a child resource that will be composed by both parent and child. So if you have the path_part defined as "active" in the child resource and it’s mounted in the users resource, the child resource will listen to a users/active endpoint.

Method

In the method resource is were we build the specification of the endpoint we are listening. The http_method argument will have the string with what HTTP method we’re interested, the "ANY" value is, again, a special handler where we’ll accept any HTTP method that comes our way. In the case we have in hands we won’t need any authorization done in our AWS API Gateway, and that’s why the value in authorization is "NONE" . The request_parameters argument, will state that we will have something in a proxy handler required for this method and that will passed to the integration resource (described next).This resource also has some arguments related to where it should be mounted, namely the rest_api_id that is the same as the one described in the Resource resource and the resource_id that represents to what resource it should be related to.

Integration

The integration resource is related to how are we going to react to the request that we just received, it could go from passing the request to a backend, run some lambda function or even doing nothing with it. Besides the arguments previously referenced ( rest_api_id and resource_id ), we have the http_method argument that will be the same as the method resource (that’s why we link both of them with the "${aws_api_gateway_method.method.http_method}" ), the integration_http_method argument represents the HTTP method that will be done from the integration to our backend (again the "ANY" value is a special one) and we have the type argument where we configure what type of integration this is. As for the uri argument it will contain the endpoint to where we are proxying to, and the request_paramenters argument will map what we need from the method resource to our request to the backend, in our case we are are replacing the {proxy} handler present in the uri argument with the path that comes after the domain of our API Gateway.

The way that all of this can be grouped together is that a method and a integration are wrapped around a resource, the method will be responsible on how can the API Gateway be reached and the integration will be responsible in interacting with the backend. Kinda like this:

How I understand all works

So with all this we’ll be able to apply this Terraform file and (hopefully) have our first AWS API Gateway all working!! 🎉 If we go to https://api-gateway.execute-api.{region}.amazonaws.com (the {region} is the region where you’ll deploy the API, i.e eu-west-1) we’ll be communicating with our API Gateway. But there’s something still bugging you, right? Do we really want to tell the our costumers to make requests to that URI? They’ll probably think it’s a scam..

Mandatory Carlos Matos gif

Rest at ease because AWS have it covered, and so does Terraform. To accomplish that we need to add two resources to our file:

resource "aws_api_gateway_domain_name" "domain" {
  domain_name = "totallynotascam.com"
  certificate_name = "example-api"
  certificate_body = "${file("${path.module}/example.com/example.crt")}"
  certificate_chain = "${file("${path.module}/example.com/ca.crt")}"
  certificate_private_key = "${file("${path.module}/example.com/example.key")}"  
}
resource "aws_api_gateway_base_path_mapping" "base_path_mapping" {
api_id = "${aws_api_gateway_rest_api.api.id}"

domain_name = "${aws_api_gateway_domain_name.domain.domain_name}"
}

Domain

Here is where we’ll create the configuration to the domain that we have. The domain_name attribute will have the string with the name that we want for our domain, the other attributes are all optional and just an example on how you can have the certificates to that domain. As stated in the Terraform documentation about this resource this is not the way of doing it, because it will store that information in clear text.

Base_path_mapping

There isn’t much to say about this resource, it’s purpose it’s only to map the domain configuration to our API. So the api_id will have the in for our API Gateway and the domain_name links to the domain domain name that we had chosen previously.

Having this all in place we’ll be able to reach https://totallynotascam.com and send all the money that the Nigerian prince is always asking 😄.


As usual if you find this useful smash that 👏 button, and if you have anything to say just leave a comment or drop it like it’s hot @RicBrazao!

Like what you read? Give Ricardo Brazão a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.