IaC Bazaar
AWSLive-tested

API Gateway HTTP API

HTTP API with routes, Lambda/ALB integrations, custom domain, JWT authorizers, and access logs.

terraformAWS#aws
aws-apigateway-httpterraform v1.7

Verification

Live-tested

Really deployed, verified, idempotent and destroyed in a cloud sandbox.

Conformance

  • Static validation (fmt · validate · tflint)
  • Security scan: findings disclosed (Checkov)
  • Plan tests (mocked: validation rules · outputs)

Provenance

  • SHA-256 checksum
  • Signature (pending)

Functional

  • Live-tested — applied, verified, destroyed

Last verified 2026-06-11 · how we verify

Documentation

aws-apigateway-http — API Gateway HTTP API

An HTTP API (API Gateway v2) wired end to end: typed integrations (Lambda-proxy or any HTTP/ALB backend, including private VPC-Link upstreams), JWT and Lambda authorizers, routes that bind methods/paths to those backends, a stage with throttling and structured JSON access logs, and an optional regional custom domain (ACM-backed, TLS 1.2 floor, optional mutual TLS). Works with Terraform and OpenTofu (>= 1.6), AWS provider >= 6.0, < 7.0.

Status: static-validated, live-test pending. Ships under live-test quarantine — validated with tofu fmt, tofu validate, and tflint. Real apply → curl the invoke URL → destroy against an AWS account is pending a cloud sandbox. HTTP APIs are cheap and fast to test (no minimum charge, the invoke URL is the verify target), so this is first in line for the live lane.

Secure / sane defaults

  • Deny-by-default authorization. Routes that name no authorizer inherit default_authorization_type, which is AWS_IAM — unauthenticated callers are rejected unless a route is explicitly opened with authorization_type = NONE. Flip the default to NONE only for a genuinely public API.
  • CORS off by default. No CORS headers are emitted (same-origin only) until you opt in; the module refuses the allow_credentials + "*" origin combination that AWS rejects.
  • Access logs on by default, streamed as a structured JSON record (request id, source IP, route, status, latency, integration error) to a module-managed CloudWatch log group with 30-day retention (KMS optional).
  • TLS 1.2 floor on the custom domain, regional endpoint type, optional mutual-TLS client authentication via an S3 truststore.
  • Throttling on by default (burst/rate caps in default_route_settings) so a single client cannot exhaust the account-wide soft limits.
  • integration_method is dropped automatically for AWS_PROXY, and connection_id only applies to VPC_LINK integrations — invalid AWS combos are pruned for you.

Usage

module "api" {
  source = "./aws-apigateway-http"

  name = "orders"

  # CORS: explicit origins, no wildcard
  cors_configuration = {
    allow_origins = ["https://app.example.com"]
    allow_methods = ["GET", "POST"]
    allow_headers = ["authorization", "content-type"]
    max_age       = 600
  }

  integrations = {
    lambda = {
      integration_type = "AWS_PROXY"
      integration_uri  = module.orders_fn.invoke_arn
    }
    legacy = {
      integration_type   = "HTTP_PROXY"
      integration_uri    = "https://legacy.internal.example.com/{proxy}"
      integration_method = "ANY"
    }
  }

  authorizers = {
    cognito = {
      authorizer_type = "JWT"
      jwt_issuer      = "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_abc123"
      jwt_audience    = ["6m...client-id"]
    }
  }

  routes = {
    "GET /healthz"   = { integration_key = "lambda", authorization_type = "NONE" }
    "POST /orders"   = { integration_key = "lambda", authorizer_key = "cognito" }
    "ANY /legacy/{proxy+}" = { integration_key = "legacy", authorizer_key = "cognito" }
  }

  # Custom domain (regional ACM cert in the same region as the API)
  domain_name     = "api.example.com"
  certificate_arn = aws_acm_certificate.api.arn

  tags = { Environment = "prod" }
}

# Allow API Gateway to invoke the Lambda integration (grant outside the module)
resource "aws_lambda_permission" "apigw" {
  statement_id  = "AllowAPIGatewayInvoke"
  action        = "lambda:InvokeFunction"
  function_name = module.orders_fn.function_name
  principal     = "apigateway.amazonaws.com"
  source_arn    = "${module.api.execution_arn}/*"
}

For a custom domain, point DNS at the API with a Route 53 alias:

resource "aws_route53_record" "api" {
  zone_id = var.zone_id
  name    = "api.example.com"
  type    = "A"
  alias {
    name                   = module.api.domain_target_domain_name
    zone_id                = module.api.domain_hosted_zone_id
    evaluate_target_health = false
  }
}

Inputs (key)

NameTypeDefaultDescription
namestringAPI name, 1–128 chars (required)
integrationsmap(object){}Backends keyed by logical name: { integration_type, integration_uri, integration_method?, payload_format_version?, connection_type?, connection_id?, timeout_milliseconds?, tls_server_name_to_verify?, request_parameters? }
authorizersmap(object){}JWT (jwt_issuer+jwt_audience) or REQUEST Lambda (authorizer_uri, …) authorizers keyed by name
routesmap(object){}Route-key => { integration_key, authorizer_key?, authorization_type?, authorization_scopes? }
default_authorization_typestring"AWS_IAM"Auth for routes with no authorizer — deny-by-default; set NONE for public
cors_configurationobjectnullCORS; null = no CORS headers
disable_execute_api_endpointboolfalseTurn off the default execute-api URL (use with a custom domain)
stage_name / auto_deploystring / bool"$default" / trueStage config
throttling_burst_limit / throttling_rate_limitnumber5000 / 10000Default-route throttling (-1 = account default)
detailed_metrics_enabledbooltruePer-route CloudWatch metrics
access_logs_enabledbooltrueStream JSON access logs
access_log_destination_arnstringnullUse an existing log-group ARN instead of a managed one
access_log_formatstringnullOverride the default JSON access-log format
log_retention_days / log_kms_key_arnnumber / string30 / nullManaged log-group retention + encryption
domain_name + certificate_arnstringnullRegional custom domain + ACM cert (same region)
domain_security_policystring"TLS_1_2"Minimum TLS policy on the domain
api_mapping_keystring""Base path under the domain (empty = root)
mutual_tls_truststore_uristringnullS3 PEM truststore to enable mTLS
tagsmap(string){}Tags for all resources

Outputs

api_id, api_arn, api_endpoint, execution_arn, stage_id, stage_arn, invoke_url, integration_ids, authorizer_ids, route_ids, access_log_group_name, access_log_group_arn, domain_name, domain_target_domain_name, domain_hosted_zone_id.

Notes

  • Lambda invoke permission is granted outside the module — add an aws_lambda_permission with principal = "apigateway.amazonaws.com" and source_arn = "${module.api.execution_arn}/*", otherwise calls 500.
  • logging_level / data_trace_enabled are WebSocket-only and are intentionally absent from default_route_settings; request-level visibility for HTTP APIs comes from the JSON access logs.
  • Custom-domain certificate must be a regional ACM cert in the API's region (HTTP APIs do not use the CloudFront us-east-1 cert that REST edge endpoints require).
  • The custom domain is created but DNS is yours to wire (Route 53 alias to domain_target_domain_name / domain_hosted_zone_id).

Requirements

  • Terraform or OpenTofu >= 1.6
  • hashicorp/aws >= 6.0, < 7.0

Verification

Static-validated (tofu fmt, tofu validate, tflint). Live apply/curl/destroy testing pending cloud sandbox availability — see catalog status.

License

Commercial — LicenseRef-IaCBazaar-Commercial. © IaC Bazaar. Original work (not derived from a third-party module).