API Gateway HTTP API と Lambda Proxy 統合
動的バックエンドの基本パターンが API Gateway + Lambda。本記事では HTTP API と REST API の選び分け、Lambda Proxy 統合の仕組み、Lambda の設定(Node.js 20 / arm64 / 256MB / 10s)について書きます。
HTTP API vs REST API
API Gateway には 2 種類あります:
| 項目 | HTTP API | REST API |
|---|---|---|
| 料金 | $1.00 / 100 万 req(最初) | $3.50 / 100 万 req(最初) |
| レイテンシ | 低い | 中 |
| 機能 | 必要十分 | API Key / Usage Plan / Request Validator 等が豊富 |
| 用途 | シンプルな API | API as a Product(API 自体を商品化) |
| 払い出される URL | https://xxx.execute-api.region.amazonaws.com | 同上 + ステージ |
本シリーズは「問い合わせフォーム 1 個」だけなので HTTP API 一択。料金 1/3 でレイテンシも低い。
Lambda Proxy 統合 (AWS_PROXY)
API Gateway → Lambda の繋ぎ方は複数ありますが、現代的なベストプラクティスは Lambda Proxy 統合:
- API Gateway は HTTP リクエストを そのまま Lambda に渡す(変換しない)
- Lambda は
eventオブジェクトから path / method / body / headers を読む - Lambda が返す JSON が
statusCode/headers/body構造で API Gateway のレスポンスになる
これにより API Gateway 側の設定がシンプルになり、Lambda 内ですべて完結する設計が可能。
本シリーズの backend_api モジュール
このモジュールが作るリソース 11 個:
aws_dynamodb_tabledata "archive_file"— Lambda zip パッケージングaws_cloudwatch_log_group— Lambda ログaws_iam_role— Lambda 実行ロールaws_iam_role_policy— Lambda 用最小権限aws_lambda_functionaws_apigatewayv2_api— HTTP APIaws_apigatewayv2_stage— $default + throttlingaws_apigatewayv2_integration— API GW → Lambdaaws_apigatewayv2_route× 2 — POST と OPTIONSaws_lambda_permission— API GW から Lambda 呼出許可
API Gateway HTTP API 本体
resource "aws_apigatewayv2_api" "this" {
name = "${var.name_prefix}-api"
protocol_type = "HTTP" # HTTP API を意味する
description = "iigtn ${var.name_prefix} HTTP API"
cors_configuration {
allow_origins = [var.allowed_origin] # https://lab.iigtn.com
allow_methods = ["POST", "OPTIONS"]
allow_headers = ["Content-Type"]
max_age = 300
}
}
protocol_type = "HTTP" で HTTP API。"WEBSOCKET" もある。CORS は API Gateway 側でも設定可能(Lambda 側でも可)。
Stage($default で AutoDeploy)
resource "aws_apigatewayv2_stage" "default" {
api_id = aws_apigatewayv2_api.this.id
name = "$default"
auto_deploy = true
default_route_settings {
detailed_metrics_enabled = true
throttling_burst_limit = 100
throttling_rate_limit = 50
}
}
HTTP API では $default ステージを使うと URL が https://xxx.execute-api.../ のシンプルな形になります。ステージ名のサフィックス付きにならない。
Throttling 100 burst / 50 rps は個人サイトでは余裕。攻撃想定するならもっと厳しく。
Integration(API GW → Lambda)
resource "aws_apigatewayv2_integration" "contact" {
api_id = aws_apigatewayv2_api.this.id
integration_type = "AWS_PROXY" # Lambda Proxy 統合
integration_uri = aws_lambda_function.contact.invoke_arn
integration_method = "POST"
payload_format_version = "2.0" # HTTP API 用 (REST は "1.0")
}
payload_format_version 2.0 はリクエスト構造が event.requestContext.http 配下に整理されています。新しい Lambda コードは 2.0 前提で書く。
Route(POST と OPTIONS)
resource "aws_apigatewayv2_route" "contact_post" {
api_id = aws_apigatewayv2_api.this.id
route_key = "POST /api/contact"
target = "integrations/${aws_apigatewayv2_integration.contact.id}"
}
resource "aws_apigatewayv2_route" "contact_options" {
api_id = aws_apigatewayv2_api.this.id
route_key = "OPTIONS /api/contact"
target = "integrations/${aws_apigatewayv2_integration.contact.id}"
}
OPTIONS は CORS preflight 用。API Gateway の cors_configuration が自動応答してくれますが、Lambda 側でも返せるように両方設定。
Lambda Permission(API GW から呼出許可)
resource "aws_lambda_permission" "apigw" {
statement_id = "AllowExecutionFromAPIGateway"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.contact.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.this.execution_arn}/*/*"
}
これが無いと API GW から Lambda を呼び出そうとして AccessDenied になります。Lambda は IAM Role だけでは「他から呼べる」ようにならない。リソースベースポリシーで「特定の API GW からの InvokeFunction」を明示的に許可する必要がある。
Lambda 関数本体
resource "aws_lambda_function" "contact" {
function_name = "${var.name_prefix}-contact"
description = "Contact form handler"
role = aws_iam_role.lambda.arn
handler = "index.handler"
runtime = "nodejs20.x" # Node.js 20 LTS
architectures = ["arm64"] # Graviton (約 20% 安)
timeout = 10 # 10 秒
memory_size = 256 # 256 MB
filename = data.archive_file.lambda.output_path
source_code_hash = data.archive_file.lambda.output_base64sha256
environment {
variables = {
DDB_TABLE = aws_dynamodb_table.contacts.name
SES_FROM = var.ses_from
SES_TO = var.ses_to
ALLOWED_ORIGIN = var.allowed_origin
}
}
depends_on = [aws_cloudwatch_log_group.contact]
}
Lambda 設定の選び方
| 項目 | 選択 | 理由 |
|---|---|---|
| runtime | nodejs20.x | LTS。AWS SDK v3 がプリインストール |
| architectures | arm64 (Graviton) | x86_64 より 約 20% 安く性能ほぼ同等 |
| timeout | 10 秒 | SES 含めても十分。長くする意味なし |
| memory_size | 256 MB | DDB+SES なら速い。CPU はメモリに比例なので増やすと高速になるが料金倍 |
archive_file で Lambda パッケージ作成
data "archive_file" "lambda" {
type = "zip"
source_dir = var.lambda_source_dir # ../../../backend/functions/contact
output_path = "${path.module}/.build/contact_lambda.zip"
}
apply 時に自動で zip 化されます。output_base64sha256 をハッシュとして aws_lambda_function に渡すと、コード変更が検知されて自動更新される。
CloudWatch Log Group の事前作成
resource "aws_cloudwatch_log_group" "contact" {
name = "/aws/lambda/${var.name_prefix}-contact"
retention_in_days = var.log_retention_days # 14 日
tags = var.tags
}
Lambda IAM Role の最小権限
data "aws_iam_policy_document" "lambda_policy" {
# CloudWatch Logs (この log group のみ)
statement {
actions = ["logs:CreateLogStream", "logs:PutLogEvents"]
resources = ["${aws_cloudwatch_log_group.contact.arn}:*"]
}
# DynamoDB (このテーブルの PutItem のみ)
statement {
actions = ["dynamodb:PutItem"]
resources = [aws_dynamodb_table.contacts.arn]
}
# SES SendEmail
statement {
actions = ["ses:SendEmail", "ses:SendRawEmail"]
resources = ["*"]
}
}
「ログ書込 + DDB Put + SES Send だけ」の最小権限。他のリソースには触れない。
動作確認
curl -X POST "https://xxx.execute-api.ap-northeast-1.amazonaws.com/api/contact" \
-H "Content-Type: application/json" \
-d '{"name":"Test","email":"test@example.com","message":"Hello world testing"}'
# {"ok":true}
次の記事
API Gateway + Lambda が動いたら、次は DynamoDB のテーブル設計 について書きます。問い合わせデータをどう保存するか、PII(個人情報)の扱い、PK 選定の考え方、PITR の有効化等を解説します。
📚 用語集
- API Gateway
- AWS のマネージド API エンドポイント。HTTP / REST / WebSocket を受け付けて Lambda 等のバックエンドに振り分ける。
- HTTP API
- API Gateway の新世代タイプ。REST API より約 1/3 の価格、低レイテンシ、シンプル。
- REST API
- API Gateway の旧世代タイプ。API Key / Usage Plan / Request Validator 等の高機能を持つが、料金が高め。
- WebSocket API
- API Gateway の WebSocket 対応タイプ。リアルタイム双方向通信用。
- Lambda Proxy 統合 (AWS_PROXY)
- API Gateway が HTTP リクエストを Lambda にそのまま渡し、Lambda が返す JSON をレスポンスとして返す統合方式。シンプルで現代的なベストプラクティス。
- payload_format_version
- Lambda Proxy 統合の event 構造のバージョン。HTTP API は 2.0、REST API は 1.0。新規構築は 2.0。
- $default ステージ
- HTTP API で URL がシンプルになる特殊なステージ名。
https://xxx.execute-api.../形式(ステージ名がパスに入らない)。 - Throttling
- API Gateway の流量制御。
burst_limit(一時的な瞬発上限)とrate_limit(持続的な秒間上限)。 - Lambda
- AWS のサーバレス関数実行サービス。アクセス無い時は完全無料、リクエスト発生時のみ実行課金。
- runtime (Lambda)
- Lambda が走らせる言語ランタイム。
nodejs20.x/python3.12/java21等。 - arm64 / Graviton
- AWS の ARM ベースプロセッサ。Lambda で arm64 を選ぶと x86_64 より約 20% 安く、性能ほぼ同等。
- memory_size (Lambda)
- Lambda の割当メモリ。CPU はメモリに比例。256 MB が無料枠との兼ね合いで標準的。
- timeout (Lambda)
- Lambda の最大実行時間。デフォルト 3 秒、最大 15 分。フォーム処理なら 10 秒で十分。
- archive_file (Terraform data source)
- ディレクトリを zip にまとめる Terraform data source。Lambda コードのパッケージング用。
- source_code_hash
- Lambda コードの SHA256 ハッシュ。これが変わると Lambda が更新される。
- aws_lambda_permission
- Lambda のリソースベースポリシー。「誰がこの Lambda を InvokeFunction できるか」を定義。API Gateway から呼ぶには必須。
- execution_arn (API Gateway)
- Lambda Permission の
source_arnに使う API Gateway の ARN。${execution_arn}/*/*でステージ・メソッドを wildcard 指定。 - CloudWatch Log Group
- CloudWatch Logs のログ集約単位。Lambda 関数 1 個に 1 個の log group が紐付く。
/aws/lambda/<関数名>の名前が慣例。 - retention_in_days
- CloudWatch Log Group の保持日数。明示しないと無期限保持になりコスト膨張する。14 日 / 30 日が定番。
- environment.variables (Lambda)
- Lambda に渡す環境変数。コード内で
process.env.NAMEで読める。 - CORS preflight
- ブラウザがクロスオリジン POST 等をする前に送る OPTIONS リクエスト。「この origin からこのメソッドで呼んでいいか」を確認する。