Setting up AWS API Gateway with Terraform

A comprehensive guide to deploying and managing API Gateway using Terraform Infrastructure as Code

Setting up AWS API Gateway with Terraform

Amazon API Gateway is a fully managed service that makes it easy for developers to create, publish, maintain, monitor, and secure APIs. This guide shows how to set up API Gateway using Terraform.

Video Tutorial

Learn more about managing AWS API Gateway with Terraform in this comprehensive video tutorial:

View Source Code

Related Guides:

Prerequisites

  • AWS CLI configured
  • Terraform installed
  • Basic understanding of REST APIs

Project Structure

aws-api-gateway-terraform/
├── main.tf
├── variables.tf
├── outputs.tf
└── terraform.tfvars

Basic API Gateway Configuration

provider "aws" {
  region = var.aws_region
}

# HTTP API
resource "aws_apigatewayv2_api" "main" {
  name          = "${var.project_name}-api"
  protocol_type = "HTTP"
  description   = "HTTP API Gateway"

  cors_configuration {
    allow_origins = ["*"]
    allow_methods = ["GET", "POST", "PUT", "DELETE"]
    allow_headers = ["content-type"]
    max_age      = 300
  }
}

# API Stage
resource "aws_apigatewayv2_stage" "main" {
  api_id      = aws_apigatewayv2_api.main.id
  name        = var.environment
  auto_deploy = true

  access_log_settings {
    destination_arn = aws_cloudwatch_log_group.api_gateway.arn
    format = jsonencode({
      requestId      = "$context.requestId"
      ip            = "$context.identity.sourceIp"
      requestTime   = "$context.requestTime"
      httpMethod    = "$context.httpMethod"
      routeKey      = "$context.routeKey"
      status        = "$context.status"
      protocol      = "$context.protocol"
      responseTime  = "$context.responseLatency"
      responseLength = "$context.responseLength"
    })
  }
}

# CloudWatch Log Group
resource "aws_cloudwatch_log_group" "api_gateway" {
  name              = "/aws/api-gateway/${var.project_name}"
  retention_in_days = 14
}

API Routes and Integrations

# Mock Integration
resource "aws_apigatewayv2_integration" "mock" {
  api_id           = aws_apigatewayv2_api.main.id
  integration_type = "MOCK"

  request_parameters = {
    "overwrite:statusCode" = "200"
  }

  response_parameters {
    status_code = "200"
    mappings = {
      "append:header.Content-Type" = "application/json"
    }
  }
}

# Route
resource "aws_apigatewayv2_route" "mock" {
  api_id    = aws_apigatewayv2_api.main.id
  route_key = "GET /test"
  target    = "integrations/${aws_apigatewayv2_integration.mock.id}"
}

Advanced Features

1. Custom Domain

resource "aws_apigatewayv2_domain_name" "main" {
  domain_name = "api.${var.domain_name}"

  domain_name_configuration {
    certificate_arn = var.certificate_arn
    endpoint_type   = "REGIONAL"
    security_policy = "TLS_1_2"
  }
}

resource "aws_apigatewayv2_api_mapping" "main" {
  api_id      = aws_apigatewayv2_api.main.id
  domain_name = aws_apigatewayv2_domain_name.main.id
  stage       = aws_apigatewayv2_stage.main.id
}

resource "aws_route53_record" "api" {
  name    = aws_apigatewayv2_domain_name.main.domain_name
  type    = "A"
  zone_id = var.hosted_zone_id

  alias {
    name                   = aws_apigatewayv2_domain_name.main.domain_name_configuration[0].target_domain_name
    zone_id                = aws_apigatewayv2_domain_name.main.domain_name_configuration[0].hosted_zone_id
    evaluate_target_health = false
  }
}

2. Request Validation

resource "aws_apigatewayv2_model" "request" {
  api_id       = aws_apigatewayv2_api.main.id
  content_type = "application/json"
  name         = "RequestModel"

  schema = jsonencode({
    type = "object"
    properties = {
      name = {
        type = "string"
      }
      email = {
        type = "string"
        format = "email"
      }
    }
    required = ["name", "email"]
  })
}

resource "aws_apigatewayv2_route" "validated" {
  api_id    = aws_apigatewayv2_api.main.id
  route_key = "POST /users"
  target    = "integrations/${aws_apigatewayv2_integration.mock.id}"

  request_model_selection_expression = "$request.method"
  model_selection_expression        = "$request.body"
  model_name                        = aws_apigatewayv2_model.request.name
}

3. API Key and Usage Plans

resource "aws_api_gateway_api_key" "main" {
  name = "${var.project_name}-key"
}

resource "aws_api_gateway_usage_plan" "main" {
  name = "${var.project_name}-usage-plan"

  api_stages {
    api_id = aws_apigatewayv2_api.main.id
    stage  = aws_apigatewayv2_stage.main.id
  }

  quota_settings {
    limit  = 1000
    period = "MONTH"
  }

  throttle_settings {
    burst_limit = 100
    rate_limit  = 50
  }
}

resource "aws_api_gateway_usage_plan_key" "main" {
  key_id        = aws_api_gateway_api_key.main.id
  key_type      = "API_KEY"
  usage_plan_id = aws_api_gateway_usage_plan.main.id
}

Monitoring Configuration

# CloudWatch Dashboard
resource "aws_cloudwatch_dashboard" "api_gateway" {
  dashboard_name = "${var.project_name}-api-gateway"

  dashboard_body = jsonencode({
    widgets = [
      {
        type   = "metric"
        x      = 0
        y      = 0
        width  = 12
        height = 6

        properties = {
          metrics = [
            ["AWS/ApiGateway", "4XXError", "ApiId", aws_apigatewayv2_api.main.id],
            ["AWS/ApiGateway", "5XXError", "ApiId", aws_apigatewayv2_api.main.id],
            ["AWS/ApiGateway", "Count", "ApiId", aws_apigatewayv2_api.main.id],
            ["AWS/ApiGateway", "Latency", "ApiId", aws_apigatewayv2_api.main.id]
          ]
          period = 300
          stat   = "Sum"
          region = var.aws_region
          title  = "API Gateway Metrics"
        }
      }
    ]
  })
}

# CloudWatch Alarms
resource "aws_cloudwatch_metric_alarm" "api_5xx" {
  alarm_name          = "${var.project_name}-5xx-errors"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = "1"
  metric_name         = "5XXError"
  namespace           = "AWS/ApiGateway"
  period             = "300"
  statistic          = "Sum"
  threshold          = "0"
  alarm_description  = "This metric monitors API Gateway 5XX errors"
  alarm_actions      = [var.sns_topic_arn]

  dimensions = {
    ApiId = aws_apigatewayv2_api.main.id
  }
}

Best Practices

  1. Security

    • Use API keys for authentication
    • Implement proper CORS settings
    • Enable WAF protection
    • Use SSL/TLS for all endpoints
  2. Performance

    • Enable caching when appropriate
    • Use proper throttling settings
    • Monitor API latency
    • Implement proper timeouts
  3. Monitoring

    • Set up detailed logging
    • Configure alarms for errors
    • Monitor API usage
    • Track latency metrics
  4. Cost Optimization

    • Use appropriate caching strategies
    • Implement proper throttling
    • Monitor API usage
    • Clean up unused resources

Common Use Cases

  1. Public API
resource "aws_apigatewayv2_api" "public" {
  name          = "${var.project_name}-public-api"
  protocol_type = "HTTP"
  
  cors_configuration {
    allow_origins = ["*"]
    allow_methods = ["*"]
    allow_headers = ["*"]
  }
}
  1. Private API
resource "aws_apigatewayv2_api" "private" {
  name                         = "${var.project_name}-private-api"
  protocol_type               = "HTTP"
  disable_execute_api_endpoint = true

  vpc_endpoint_ids = [aws_vpc_endpoint.api_gateway.id]
}

resource "aws_vpc_endpoint" "api_gateway" {
  vpc_id             = var.vpc_id
  service_name       = "com.amazonaws.${var.aws_region}.execute-api"
  vpc_endpoint_type  = "Interface"
  subnet_ids         = var.subnet_ids
  security_group_ids = [aws_security_group.api_gateway.id]
}

Variables

variable "aws_region" {
  description = "AWS region"
  type        = string
  default     = "us-west-2"
}

variable "project_name" {
  description = "Project name"
  type        = string
}

variable "environment" {
  description = "Environment name"
  type        = string
  default     = "dev"
}

variable "domain_name" {
  description = "Domain name for API"
  type        = string
}

variable "certificate_arn" {
  description = "ACM certificate ARN"
  type        = string
}

variable "hosted_zone_id" {
  description = "Route53 hosted zone ID"
  type        = string
}

variable "vpc_id" {
  description = "VPC ID for private API"
  type        = string
}

variable "subnet_ids" {
  description = "Subnet IDs for private API"
  type        = list(string)
}

variable "sns_topic_arn" {
  description = "SNS Topic ARN for alarms"
  type        = string
}

Outputs

output "api_endpoint" {
  description = "API Gateway endpoint URL"
  value       = aws_apigatewayv2_api.main.api_endpoint
}

output "api_id" {
  description = "API Gateway ID"
  value       = aws_apigatewayv2_api.main.id
}

output "stage_name" {
  description = "API Gateway stage name"
  value       = aws_apigatewayv2_stage.main.name
}

output "custom_domain" {
  description = "Custom domain name"
  value       = aws_apigatewayv2_domain_name.main.domain_name
}

Conclusion

This setup provides a comprehensive foundation for deploying API Gateway using Terraform. Remember to:

  • Implement proper security measures
  • Set up monitoring and alerting
  • Configure appropriate throttling
  • Use proper logging and tracing

For integrating this API Gateway with Lambda functions, see our guide on AWS Lambda with API Gateway.