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 "awsregion" {
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.