Terraform AWS API Gateway REST API: Code-Defined Serverless Front Door

API Gateway is the front door for your serverless applications on AWS. It handles routing, throttling, authentication, and more - all without you managing a single server. But configuring it through the console is tedious and error-prone. There are so many interconnected pieces that clicking through them manually just doesn't scale.

Terraform solves this by letting you define your entire API as code. In this post, we'll build both a REST API and an HTTP API with Terraform, covering Lambda integration, stages, custom domains, and authorization.

REST API vs HTTP API

AWS offers two types of API Gateway: REST API v1 and HTTP API v2.

Feature REST API HTTP API
Feature set More features Simpler
Capabilities request/response transformations, WAF integration, caching, API keys Cheaper, faster
Use case Existing projects needing advanced features New projects

REST API has more features such as request/response transformations, WAF integration, caching, API keys. HTTP API is cheaper, faster, and simpler.

Solution Architecture

Core Components

The architecture consists of three main layers:

OpenAPI Specification Layer: A YAML file containing all API route definitions, HTTP methods, request/response schemas, and AWS-specific integration extensions. This file serves as both the API definition and documentation source.

Infrastructure Module: Reusable Terraform code that creates the API Gateway REST API, deployment, and stage resources. The module imports the OpenAPI specification and configures automatic redeployment using SHA1 hash triggers.

Environment Configuration: Environment-specific settings for dev, staging, production that reference the infrastructure module with appropriate parameters

This approach offers automatic documentation, version control for API changes, and seamless integration with AWS API Gateway.

This article demonstrates how to implement a production-ready Terraform project that deploys AWS API Gateway using OpenAPI specifications. The solution includes automatic redeployment triggers when API definitions change, multi-environment support, and proper state management.

Input Variables

Create the variables file to accept environment-specific parameters:

```hcl

infrastructure/variables.tf

variable "projectname" {
description = "Project name"
type = string
}
variable "environment" {
description = "Environment (dev, stg, prod)"
type = string
}
variable "aws
region" {
description = "AWS region"
type = string
}
```

Creating the REST API Resource

Create the main infrastructure file that imports the OpenAPI specification:

```hcl

infrastructure/awsapigateway.tf

resource "awsapigatewayrestapi" "api" {
name = "${var.project_name}-${var.environment}"
description = "API Gateway POC - OpenAPI Specification"
body = file("${path.module}/openapi.yaml")
}
```

The REST API resource uses the OpenAPI specification as the body. When the file content changes, Terraform automatically triggers a new API Gateway deployment.

Deployment with Automatic Redeployment Triggers

hcl resource "aws_api_gateway_deployment" "deploy" { rest_api_id = aws_api_gateway_rest_api.api.id triggers = { redeployment = sha1(file("${path.module}/openapi.yaml")) } lifecycle { create_before_destroy = true } }

The trigger uses a SHA1 hash of the OpenAPI file. Any change to the specification forces a new deployment.

Stage Configuration

hcl resource "aws_api_gateway_stage" "stage" { stage_name = var.environment rest_api_id = aws_api_gateway_rest_api.api.id deployment_id = aws_api_gateway_deployment.deploy.id }

The modular structure allows deploying the same API definition across multiple environments with different configurations, following the DRY principle.

Backend Configuration and Initialization

Edit backend.hcl with your S3 bucket details:

bucket = "<your-terraform-state-bucket>" key = "api-gateway-openapi/dev/terraform.tfstate" region = "us-east-1"

Initialize Terraform with the backend configuration:

terraform init -backend-config=backend.hcl

This command:
- Downloads the AWS provider
- Configures the S3 backend for state storage
- Initializes the infrastructure module

Execution Plan and Apply

Generate and review the execution plan:

terraform plan -out=tfplan

Expected output:
```
Terraform will perform the following actions:

module.apigateway.awsapigatewaydeployment.deploy will be created

module.apigateway.awsapigatewayrest_api.api will be created

module.apigateway.awsapigatewaystage.stage will be created

Plan: 3 to add, 0 to change, 0 to destroy.
```

Deploy the infrastructure:

terraform apply "tfplan"

After successful deployment, Terraform displays the API endpoint:
Outputs: api_url = "https://abc123def4.execute-api.us-east-1.amazonaws.com/dev"

Testing the Deployed API

Get the API endpoint from Terraform outputs:

terraform output api_url

Test the GET /users endpoint:

curl https://<api-id>.execute-api.us-east-1.amazonaws.com/dev/users

Expected response:
{ "message": "GET /users - Request received successfully", "users": ["user1", "user2"] }

Test the GET /products endpoint:
curl https://<api-id>.execute-api.us-east-1.amazonaws.com/dev/products

Logging Configuration

Enable CloudWatch Logging
Add logging configuration to the stage resource:

hcl resource "aws_api_gateway_stage" "stage" { stage_name = var.environment rest_api_id = aws_api_gateway_rest_api.api.id deployment_id = aws_api_gateway_deployment.deploy.id access_log_settings { destination_arn = aws_cloudwatch_log_group.api_logs.arn format = jsonencode({ requestId = "$context.requestId" ip = "$context.identity.sourceIp" requestTime = "$context.requestTime" httpMethod = "$context.httpMethod" resourcePath = "$context.resourcePath" status = "$context.status" protocol = "$context.protocol" responseLength = "$context.responseLength" }) } }

hcl resource "aws_cloudwatch_log_group" "api_logs" { name = "/aws/apigateway/${var.project_name}-${var.environment}" retention_in_days = 7 }

Throttling and Quotas

Without them, a single client can overwhelm your backend:

hcl resource "aws_api_gateway_method_settings" "all" { rest_api_id = aws_api_gateway_rest_api.my_api.id stage_name = aws_api_gateway_stage.prod.stage_name method_path = "*/*" settings { throttling_burst_limit = 100 throttling_rate_limit = 50 metrics_enabled = true logging_level = "INFO" } }

Implement Throttling and Quotas
Add usage plans to control API access:

hcl resource "aws_api_gateway_usage_plan" "plan" { name = "${var.project_name}-${var.environment}-plan" api_stages { api_id = aws_api_gateway_rest_api.api.id stage = aws_api_gateway_stage.stage.stage_name } throttle_settings { burst_limit = 100 rate_limit = 50 } quota_settings { limit = 10000 period = "DAY" } }

Security Recommendations

Use Custom Domain Names: Configure custom domain names with SSL certificates for production

Multi-Environment Support

The modular structure allows deploying the same API definition across multiple environments with different configurations, following the DRY principle.

Destroy and Cleanup

$ terraform destroy

Plan:
Plan: 0 to add, 0 to change, 14 to destroy. Changes to Outputs: - base_url = "https://1q6qs02fjc.execute-api.us-east-1.amazonaws.com/serverless_lambda_stage" -> null - function_name = "HelloWorld-rs" -> null - lambda_bucket_name = "learn-terraform-functions-reasonably-highly-firm-honeybee" -> null Do you really want to destroy all resources in workspace "learn-terraform-lambda-api-gateway"? Terraform will destroy all your managed infrastructure, as shown above. There is no undo. Only 'yes' will be accepted to confirm. Enter a value: yes

Apply complete! Resources: 0 added, 0 changed, 14 destroyed.

If you used HCP Terraform for this tutorial, after destroying your resources, delete the learn-terraform-lambda-api-gateway workspace from your HCP Terraform organization.

In this tutorial, you created and updated an AWS Lambda function with an API Gateway integration. These components are essential parts of most serverless applications.

Conclusion

API Gateway with Terraform can feel verbose, especially the REST API variant. But once you have it working, you've got a repeatable, version-controlled API infrastructure. Use HTTP API for new projects unless you specifically need REST API features. And always set up proper logging and throttling before going to production.

The OpenAPI-driven workflow provides automatic redeployment when definitions change, multi-environment support through variables, and proper state management via S3 backend. Throttling settings for the stage protect backend systems, CloudWatch logging provides observability, and usage plans enforce quotas. Together these patterns form a production-ready foundation for serverless APIs defined as code.

Sources

  1. Oneuptime Create API Gateway with Terraform
  2. DevOps Dev How to Deploy AWS API Gateway with OpenAPI Specification Using Terraform
  3. Hashicorp Developer Terraform Tutorials AWS Lambda API Gateway

Related Posts