API Gateway is the serverless managed solution provided by Amazon Web Services (AWS) to allow users to implement REST and HTTP APIs. This service has a bewildering number of configuration points and can become complex quickly. Yet, it is still very useful as you don’t have to manage any servers to use it, and it integrates very well with many other AWS services.
API Gateway can deploy many types of APIs:
- HTTP APIs, which allow more flexibility on the HTTP request
- REST APIs, which are more strict but offer more features
- Websocket APIs, which are obviously used to allow websockets
In this article, we will use a REST API, which is likely the type of API most projects would use. It will show you how to deploy a simple API Gateway backed by a Lambda function using purely Terraform. For the purpose of this guide, we will limit ourselves to a basic configuration for API Gateway. More advanced configuration options will be mentioned without necessarily going into details. This article will provide the Terraform code to run this example, so you can try it for yourself.
For this example, we will write a dummy Lambda function in Python. It will just return a fixed response. Here is its code:
The Lambda function must fulfill certain requirements in order to be used by API Gateway. The “event” argument holds all the input parameters that can be provided by API Gateway, so the code must look for what it needs from this argument. Also, the return value must be a map containing at least two fields:
- “statusCode”: this is the HTTP code indicating the status of the processing of the request (as a number)
- “body”: the content of the HTTP response (as a string)
In most cases, these are the values that will be returned to the client, but not always. API Gateway has the capacity to insert some processing before and after invoking the backend, and it can override those values.
In order to be able to run, the Lambda function must have an IAM role attached to it. It is our responsibility to create that role, which will be done in Terraform obviously. Because our Lambda function is very basic, it doesn’t need a lot of permissions to run. If your code needs to access some other AWS services, you will need to add the necessary permissions to the IAM role.
The Terraform code to manage the Lambda function will look like this:
A few comments about the above code:
- The IAM role allows the Lambda function to create log groups and log streams, and to store log entries. This is the minimum set of permissions required to run a Lambda function.
- To create the Lambda function, we need to give the function’s code to AWS in the form of a ZIP file. Hence we need to use an intermediary “archive_file” object in order to create the ZIP file from the function’s code.
- In the “aws_lambda_function” resource, the “handler” parameter must be formatted as “FILE_NAME.FUNCTION_NAME”, where FILE_NAME is the name of the source code file, and FUNCTION_NAME is the name of the function that the Lambda runtime engine must call to run the Lambda function.
Minimum API Gateway code
Here is a screenshot of what API Gateway looks like:
We can see on this screenshot that the information flows through various steps. First, a request is received by API Gateway, and is transferred to the “method request” step. This step is typically used to parse information from the request and set parameters for the next step, which is “integration request”. This is where API Gateway invokes the configured backend, which is the Lambda function in our case.
A quick and important note here: API Gateway offers two types of integration with backends: “proxy” and “non-proxy”. In summary, the “proxy” integration type essentially forwards the requests and responses “as is” between the client and the backend. On the other hand, the “non-proxy” integration type allows you to make modifications to both the requests and the responses; hence this integration type is sometimes called “custom”. The AWS documentation has more information if (or actually when) you need to make a decision on which integration type to use.
Let’s go back to the data flow in API Gateway. The third and fourth steps are the integration response and the method response, which are mostly used to modify the response sent by the backend. These steps are available only if the integration type is “non-proxy”.
In our toy example, we use the “proxy” integration type, and thus both the request and the response are not modified by API Gateway. Should you require the ability to modify requests and responses, you will need to set the “type” to “AWS” (instead of “AWS_PROXY”) in the “aws_api_gateway_integration” resource in the Terraform code.
The data flow we just explored applies to a given resource (which essentially translate into an HTTP path) and a given method (i.e. HTTP method such as GET, POST, etc.) In our tiny project, we will have only one resource (named “widget”) and one method (“GET” in our case).
So given the above, the first part of our Terraform code will look like this:
api_gateway.tf (part 1)
The next part of Terraform code defines the integration with the Lambda function:
api_gateway.tf (part 2)
We also give permissions to API Gateway to call our Lamda function, which is done with the last resource, “aws_lambda_permission”. The “data aws_caller_identity” is only used to get the AWS account ID, which is part of the ARN that is constructed in the “aws_lambda_permission” resource.
If you copied the code snippets in this article and saved them to the corresponding files, you should have a working piece of code that can deploy a basic API Gatway! Now, here is how to test it.
To deploy the code, run the following in the directory where you saved the files:
And wait for a couple of minutes until Terraform finishes its job.
Now open your browser and login to the AWS console. Then, navigate to the API Gateway service and click on the API you just created (make sure you have selected the correct region!). In the “Resources” column, under the resource you created (which should be “/widget”), click on “GET”.
The right pane will now show information about the “GET” method for the “/widget” resource, which is what Terraform just created for us. Now you should see a “test” link on the left side of the pane:
Leave all the parameters to their default (mostly empty) values and click on the “Test” button. You should very quickly see the result of the test on the right side of the screen, including the API Gateway logs for this request.
You can also view the logs for the Lambda function invocation itself too. You will need to navigate to the “Lambda” console and click on the “backend” function (or however you called it). If you scroll down a little bit, you will see a “Monitor” tab. Select it and now have access to all sorts of monitoring aspects, such as the logs for the Lambda function and various metrics. This is very helpful for debugging.
This article was just a quick tutorial showing a basic API Gateway setup backed by a Lambda function. It could obviously serve as a reference point to build a more complete API and functional API.
Please note that configuring a complex setup for API Gateway would be quite cumbersome to do using pure Terraform code. That’s where using an OpenAPI definition file could make things a bit simpler.
Once you move beyond a simple API Gateway configuration into more complex deployments, ensuring that it has no security gaps can be tricky. That’s where third party products, such as oak9, can help.