API Gatewayの使い方:HTTP APIとREST APIの選び方とLambda統合・認証・CORSをSAMで作る
API Gatewayの使い方を実例で。HTTP APIとREST APIの違い、Lambdaプロキシ統合、Cognito/JWT認証、CORS、ステージ、スロットリングをコピペで動くSAMテンプレートで解説。
「とりあえずAPI作って」とClaude Codeに頼んだら、5分でLambdaとAPI Gatewayが立ち上がりました。curlも通る。感動しました。
その翌週、フロントから叩いたら Cross-Origin Request Blocked で全滅。CORSをLambda側に書いていたんですが、HTTP APIではそれが効かないんですね。さらに認証が空っぽで、問い合わせフォームのエンドポイントが世界中に公開されていました。動くAPIと、本番に出せるAPIは、別物でした。
API Gatewayでつまずくのは、Lambdaのコードではありません。「どこで認証するか」「CORSをどこで返すか」「ログに何を残すか」「制限値をいくつにするか」という境界です。ここを決めずに「API作って」と頼むと、必ず穴の空いた構成が出てきます。
この記事では、HTTP APIとREST APIの選び方から、Lambda統合・認証・CORS・ステージ・スロットリングまで、AWS公式を確認しながら、コピペで動くSAMテンプレート1本にまとめます。
この記事の要点
- 迷ったらHTTP API。SPA/モバイルからLambdaへつなぐ一般的なJSON APIなら、安くて速くてCORSもJWTも揃う。APIキー・利用プラン・WAF・リクエスト検証・キャッシュが要るならREST API。
- Lambdaプロキシ統合は、リクエストをそのままLambdaに渡してレスポンスをそのまま返す方式。マッピングを書かずに済むので最初はこれ一択。
- CORSはAPI Gateway側で設定する。HTTP APIでCORSを設定すると、API GatewayはLambdaが返したCORSヘッダーを無視する(公式明記)。Lambdaに書いても効かない。
- 認証は後付けにせず最初から設計。ユーザー向けはCognito/JWT、AWS内部はIAM、独自ルールはLambda authorizer。
- 下にコピペで動くSAMテンプレート1本(HTTP API + Lambda + CORS + アクセスログ + ルート別スロットリング)を置いた。
HTTP APIとREST APIの違いを表で決める
API Gatewayには大きくHTTP API、REST API、WebSocket APIの3種類があります。双方向通信(チャットや進捗のリアルタイム配信)が要るならWebSocket APIですが、Webやモバイルの普通のAPIで悩むのはHTTP APIとREST APIの2択です。
AWS公式の比較ページの言い方を借りると、REST APIは機能が多く、HTTP APIは機能を絞って低価格に寄せた製品です。違いを実務で効くポイントだけ並べると、こうなります。
| 機能 | HTTP API | REST API |
|---|---|---|
| JWT authorizer(Cognito/OIDC) | あり | なし(Lambda authorizerで代替) |
| IAM認証 / Lambda authorizer | あり | あり |
| APIキー・利用プラン(顧客別制限) | なし | あり |
| リクエスト検証(スキーマ) | なし | あり |
| AWS WAF | なし | あり |
| Private API / エッジ最適化 | なし(リージョナルのみ) | あり |
| キャッシュ | なし | あり |
| X-Ray トレース | なし | あり |
| 自動デプロイ | あり | なし(明示デプロイ) |
| 価格・レイテンシ | 安い・低レイテンシ寄り | 高機能ぶん割高 |
判断はシンプルです。APIキー・顧客別の利用プラン・リクエスト検証・WAF・Private APIのどれかが要件にあるならREST API。なければHTTP API。SPAやモバイルからLambdaを叩いてJWTで認証してCORSを返す、という今どきの構成はHTTP APIにきれいに収まります。
「将来REST APIの機能が要るかも」で最初からREST APIにするのは、たいてい早すぎます。後から要件が固まった時に移すほうが、設定項目が少ないぶん速いです。逆に、B2Bで取引先ごとにレート制限したい、リクエストボディをスキーマで弾きたい、という要件が最初から見えているなら、迷わずREST APIにしてください。
今回はHTTP APIで作ります。ルート・Lambda・CORS・ログ・制限の対応関係がテンプレート1枚に収まり、Claude Codeに差分でレビューさせやすいからです。
公式の比較はここにまとまっています: Choose between REST APIs and HTTP APIs。
Lambdaプロキシ統合とは何か
API GatewayとLambdaのつなぎ方には、統合の種類があります。最初に覚えるべきは**プロキシ統合(Lambdaプロキシ統合)**です。
ふつうの統合(非プロキシ)は、API Gatewayがリクエストを分解して、Lambdaに渡す形に変換し、返ってきたものをまたHTTPレスポンスに組み立て直します。マッピングテンプレートという変換ルールを自分で書く必要があり、項目が多くて事故りやすい。
プロキシ統合は逆です。リクエストを丸ごとLambdaに渡し、Lambdaが返したオブジェクトをそのままHTTPレスポンスにする。間の変換を考えなくていい。たとえるなら、受付が封筒を開けずに担当者にそのまま渡し、担当者の返事をそのまま郵送するイメージです。SAMでLambdaをHTTP APIにつなぐと、標準でこのプロキシ統合になります。
プロキシ統合のとき、Lambdaが返すべき形は決まっています。HTTP APIのペイロード形式2.0なら、こうです。
// API Gateway HTTP API(ペイロード2.0)のプロキシ統合で返す形
return {
statusCode: 200,
headers: { "content-type": "application/json" },
body: JSON.stringify({ ok: true }),
};
ここを return { ok: true } のように素のオブジェクトで返すと、502 Bad Gateway や Internal Server Error になります。プロキシ統合のいちばんありがちな事故がこれです。statusCode と body(文字列)を返す、と覚えてください。
CORSはAPI Gateway側で返す
冒頭で全滅した原因がこれでした。AWS公式の表現は、迷う余地がないほどはっきりしています。
If you configure CORS for an API, API Gateway ignores CORS headers returned from your backend integration. (CORSを設定すると、API Gatewayはバックエンド統合が返したCORSヘッダーを無視する)
つまり、HTTP APIでCORSを設定したら、Lambdaに access-control-allow-origin を書いても無視される。さらにAPI Gatewayは、OPTIONSルートを定義していなくても、プリフライトのOPTIONSリクエストに自動で応答してくれます。だからCORSの正解は「Lambdaに書く」ではなく「API Gatewayの CorsConfiguration に書く」です。
設計するとき一緒に決めるのは次の4つです。
- 許可オリジン:
https://example.comのように具体的に。*(全許可)は、認証付きAPIでは避ける。 - Cookieを使うか: 使うなら
AllowCredentials: true。ただしこのときオリジンに*は使えない(ブラウザ仕様)。 - 許可ヘッダー: 認証トークンを送るなら
authorizationを必ず入れる。 - 許可メソッド: 実際に使う
GETPOSTなどと、プリフライト用のOPTIONS。
公式: Configure CORS for HTTP APIs。
コピペで動くSAMテンプレート
ここまでの判断を1本のSAMテンプレートにまとめます。HTTP API + Lambdaプロキシ統合 + CORS + アクセスログ + ルート別スロットリングが入っています。認証はまず外して動作確認し、後で「公開ルートが残っていないか」をClaude Codeにレビューさせる流れにします。
ランタイムは nodejs22.x です。nodejs20.x は2026年4月末にサポート終了(EOL)を迎え、新規作成もできなくなっていくため、ここは22系を指定します。
template.yaml を作ります。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: HTTP API sample with Lambda proxy, CORS, access logs, and throttling
Parameters:
StageName:
Type: String
Default: Prod
AllowedOrigin:
Type: String
Default: https://example.com
Globals:
Function:
Runtime: nodejs22.x # 20.xはEOL。22系を使う
Architectures:
- arm64 # Gravitonで安く速く
Timeout: 10
MemorySize: 256
Resources:
ApiAccessLogs:
Type: AWS::Logs::LogGroup
Properties:
RetentionInDays: 14 # ログ保持はコストに直結。短めから
PublicHttpApi:
Type: AWS::Serverless::HttpApi
Properties:
StageName: !Ref StageName
CorsConfiguration: # CORSはここで返す。Lambda側は無視される
AllowOrigins:
- !Ref AllowedOrigin
AllowHeaders:
- authorization
- content-type
- x-request-id
AllowMethods:
- GET
- POST
- OPTIONS
MaxAge: 300
AccessLogSettings: # 障害対応に効く最小フィールド
DestinationArn: !GetAtt ApiAccessLogs.Arn
Format: '{"requestId":"$context.requestId","routeKey":"$context.routeKey","status":"$context.status","ip":"$context.identity.sourceIp","requestTime":"$context.requestTime","integrationError":"$context.integrationErrorMessage"}'
DefaultRouteSettings: # API全体の既定スロットリング
ThrottlingBurstLimit: 20
ThrottlingRateLimit: 10
RouteSettings:
"POST /contacts": # 書き込み系だけ厳しめに
ThrottlingBurstLimit: 5
ThrottlingRateLimit: 2
FailOnWarnings: true
HealthFunction:
Type: AWS::Serverless::Function
Properties:
Handler: src/handler.health
Events:
Health:
Type: HttpApi
Properties:
ApiId: !Ref PublicHttpApi
Path: /health
Method: GET
PayloadFormatVersion: "2.0"
ContactFunction:
Type: AWS::Serverless::Function
Properties:
Handler: src/handler.contact
Events:
Contact:
Type: HttpApi
Properties:
ApiId: !Ref PublicHttpApi
Path: /contacts
Method: POST
PayloadFormatVersion: "2.0"
TimeoutInMillis: 10000
Outputs:
ApiUrl:
Description: Invoke URL
Value: !Sub "https://${PublicHttpApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/${StageName}/"
src/handler.js を作ります。プロキシ統合の返り値の形(statusCode と文字列 body)を守るのがポイントです。
// プロキシ統合の返り値ヘルパー。必ず statusCode と文字列 body を返す
const json = (statusCode, body) => ({
statusCode,
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
});
// 疎通確認用。ここがCORSや認証の動作チェックの起点になる
export const health = async () => {
return json(200, {
ok: true,
service: "claude-code-api-gateway",
checkedAt: new Date().toISOString(),
});
};
// 問い合わせ受付。入口で入力検証して、おかしいリクエストは早く弾く
export const contact = async (event) => {
let payload;
try {
payload = JSON.parse(event.body ?? "{}");
} catch {
return json(400, { message: "Request body must be valid JSON." });
}
const name = String(payload.name ?? "").trim();
const email = String(payload.email ?? "").trim();
// 検証はLambda側でも必ずやる。API Gateway(HTTP API)にスキーマ検証はない
if (!name || !email.includes("@")) {
return json(422, {
message: "name and a valid email are required.",
requestId: event.requestContext?.requestId,
});
}
// 本番ではここでDB保存やメール送信。今は受領レスポンスだけ返す
return json(202, {
message: "accepted",
requestId: event.requestContext?.requestId,
received: { name, email },
});
};
ビルドからデプロイ、疎通確認まで(Git Bash / macOS / Linux)。
node --check src/handler.js # まず構文チェック
sam build
sam deploy --guided \
--stack-name clc-api-gateway-sample \
--parameter-overrides AllowedOrigin=https://example.com
API_URL=$(aws cloudformation describe-stacks \
--stack-name clc-api-gateway-sample \
--query "Stacks[0].Outputs[?OutputKey=='ApiUrl'].OutputValue" \
--output text)
curl "${API_URL}health"
curl -X POST "${API_URL}contacts" \
-H "content-type: application/json" \
-d '{"name":"Masa","email":"[email protected]"}'
Windows PowerShellなら、URL取得と疎通はこの形です。
$apiUrl = aws cloudformation describe-stacks `
--stack-name clc-api-gateway-sample `
--query "Stacks[0].Outputs[?OutputKey=='ApiUrl'].OutputValue" `
--output text
curl.exe ($apiUrl + "health")
curl.exe -X POST ($apiUrl + "contacts") `
-H "content-type: application/json" `
-d '{"name":"Masa","email":"[email protected]"}'
/health が 200、/contacts が 202 を返したら、入口は生きています。Lambdaの中身は AWS Lambda実装の勘所 に分けてあるので、ハンドラやコールドスタートの設計はそちらを見てください。
認証は最初から「どれを使うか」を決める
サンプルはまず動作確認を優先して公開ルートにしました。本番でここを放置すると、冒頭の僕のように問い合わせフォームを世界に開放することになります。HTTP APIの認証は、次の3つから選びます。
Cognito / JWT authorizer は、ログイン済みユーザーがブラウザやモバイルから叩くAPI向けです。CognitoやOIDCプロバイダーが発行したアクセストークンをAPI Gatewayが検証し、issuer・audience・有効期限・scope を入口でチェックします。Lambdaの中で毎回JWTライブラリを呼ぶより、入口で弾けるぶん責務がきれいに分かれます。Cognitoを使う場合もHTTP APIではJWT authorizerとして設定するのが基本です。
IAM認証 は、AWS上のワークロード同士、社内バッチ、管理ツール向けです。クライアントはSigV4で署名し、execute-api 権限を持つリクエストだけが通ります。認証に失敗したリクエストはLambdaまで届きません。ここで使う権限の絞り方は AWS IAMポリシーの書き方と最小権限 にまとめてあります。
Lambda authorizer は、独自ルールが要るときの逃げ道です。既存の会員DB照合、IP制限、取引先別の契約判定など、CognitoやIAMで表せないロジックがあるなら使います。ただしauthorizer自体の遅延・キャッシュ・障害時の影響も設計対象になります。
SAMでJWT authorizerを足すなら、HTTP APIに Authorizers を定義して、保護したいルートに紐づけます。骨格はこうです。
PublicHttpApi:
Type: AWS::Serverless::HttpApi
Properties:
StageName: !Ref StageName
Auth:
Authorizers:
CognitoJwt:
IdentitySource: "$request.header.Authorization"
JwtConfiguration:
issuer: !Sub "https://cognito-idp.${AWS::Region}.amazonaws.com/${UserPoolId}"
audience:
- !Ref UserPoolClientId
# 既定で全ルートに認証をかけ、health だけ後で明示的に外す
DefaultAuthorizer: CognitoJwt
「公開で動かす → 認証を足す → 公開のままのルートが残っていないか確認する」。この順番にすると、認証が「あとで入れるもの」になりません。
ステージとデプロイ、スロットリングの考え方
ステージ は、同じAPIの公開先(環境)を分ける仕組みです。Prod、Stg のように分け、URLは .../Prod/health のようにステージ名が入ります。HTTP APIは変更が自動デプロイされるのが特徴で、REST APIのように「デプロイ操作」を明示的に叩かなくても反映されます(REST APIは明示デプロイ)。
スロットリング は、リクエスト過多からバックエンドを守る制限です。トークンバケット方式で、定常レート(RateLimit)とバースト(BurstLimit)を持ち、超えると 429 Too Many Requests を返します。上のテンプレートでは、API全体を毎秒10・バースト20にしつつ、書き込み系の POST /contacts だけ毎秒2・バースト5に絞っています。読み取りより書き込みを厳しくするのは定石です。
ここで大事なのは、スロットリングを「絶対に超えない天井」と思わないことです。守る順番は、API Gatewayの制限 → Lambdaの同時実行数 → DBや外部APIの制限 → クライアント側のリトライ制御、と何段も重ねます。429 を受けたクライアントは指数バックオフ(待ち時間を倍々で延ばす再試行)する前提で設計してください。
アクセスログは、テンプレートで requestId・routeKey・status・integrationError をJSONで出しています。これがあると、監視アラートや問い合わせから「どのルートで何が起きたか」へ一直線で戻れます。CloudWatch Logs Insightsでの絞り込みは CloudWatch Logs Insightsとアラーム設計 を参照してください。ログにAuthorizationヘッダーや個人情報を出さないことだけは、必ず守ってください。
Claude Codeに「本番事故を探させる」レビュー
API Gatewayは入口なので、機能追加より先に穴をふさぐレビューが効きます。僕はテンプレートができたら、Claude Codeに「コードを書いて」ではなく「事故を探して」と頼みます。
このAWS SAMテンプレートのAPI Gateway設定を本番前レビューしてください。
観点:
- 認証なしで公開されているルートが残っていないか
- CORSのAllowOriginsが広すぎないか(* になっていないか)
- Cognito/JWT/IAM/Lambda authorizerのどれが適切か
- AccessLogSettingsに requestId, routeKey, status が入っているか
- Authorizationヘッダーや個人情報をログに出していないか
- ThrottlingBurstLimit/RateLimit が用途として妥当か(書き込み系を絞れているか)
- Lambda側に入力検証・タイムアウト・エラーレスポンスがあるか
- APIキーや顧客別利用プランが要件にあり、REST APIにすべきではないか
- Runtimeがサポート中のバージョンか(nodejs20.xなどEOLでないか)
修正案は差分で出してください。
一度に全部作らせないのもコツです。1回目にルート設計、2回目にSAM、3回目に認証、4回目にログとスロットリング、5回目に運用レビュー、と分けます。コンテキストが汚れたら、テンプレートとレビュー結果だけを新しいセッションに渡す。設定項目が多いAPI Gatewayでは、この刻み方が特に効きます。
よくある質問
Q. HTTP APIとREST API、結局どっちを選べばいい? A. APIキー・顧客別の利用プラン・リクエスト検証・WAF・Private API・キャッシュのどれかが要件にあるならREST API、なければHTTP APIです。SPA/モバイル + Lambda + JWT + CORS、という構成はHTTP APIに収まります。
Q. CORSをLambdaに書いたのに効きません。
A. HTTP APIでCORSを設定すると、API GatewayはLambdaが返したCORSヘッダーを無視します(公式明記)。CORSはテンプレートの CorsConfiguration に書いてください。OPTIONSへの応答もAPI Gatewayが自動でやります。
Q. プロキシ統合で 502 や Internal Server Error が出ます。
A. Lambdaの返り値の形が違います。ペイロード2.0のプロキシ統合では { statusCode, headers, body } を返し、body は文字列(JSON.stringify 済み)にします。素のオブジェクトを返すと落ちます。
Q. Cognitoで認証したいのですが、HTTP APIでできますか?
A. できます。HTTP APIではCognitoのトークンをJWT authorizerとして検証します。issuer にユーザープールのURL、audience にアプリクライアントIDを設定し、保護したいルートにauthorizerを紐づけます。
Q. スロットリングを設定すれば過負荷は完全に防げますか?
A. いいえ。トークンバケットの目標値であって、絶対の天井ではありません。Lambda同時実行数、DB/外部APIの制限、クライアントの指数バックオフを重ねて守ります。429 前提の設計にしてください。
実際に試した結果
冒頭のCORS全滅と公開フォーム事故のあと、僕はAPI Gatewayで作る順番を固定しました。①HTTP APIで足りるか表で判断 → ②SAMでルートとプロキシ統合 → ③CORSはテンプレート側に書く → ④認証(Cognito/JWT or IAM)を足す → ⑤ログとスロットリング → ⑥Claude Codeに事故レビュー。
この順番にしてから、効果が一番大きかったのは「公開ルートが残っていないか」をレビューの定型観点にしたことでした。Lambdaのコードから書き始めていた頃より、本番直前に見つかる穴が明らかに減りました。あとは地味ですが、nodejs20.x のままになっていたテンプレートを22系に直せたのも、レビュー観点に「Runtimeがサポート中か」を入れていたおかげです。API Gatewayは賢く作るより、穴のない入口を先に作るほうが、結局は速いです。
Claude CodeでAWS構成を組むときのレビュー観点や、IAM・ログの境界、戻れるチェックリストは 研修・教材ページ に整理しています。自社のAPIや問い合わせフォームに合わせて観点を作ると、Claude Codeがかなり頼れる相棒になります。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
まず無料PDFで基本を固め、繰り返し使う作業はGumroad教材へ、チーム導入や権限設計は導入相談へ進めます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
制作会社がClaude Codeに触らせる前に決める権限チェックリスト
クライアントサイトを壊さずにAI編集を使うための、制作会社向け権限と確認の型です。
SaaSサポートのバグ報告をClaude Codeで再現手順に変える実務フロー
問い合わせ文をそのまま開発へ投げず、再現手順、証拠、次の一手に整えるサポート向け手順です。
Obsidianの古いメモをClaude Codeの指示書に変える10分ルーチン
Obsidianに溜めたメモが毎回ゴミになる人へ。事実・決定・未確認に仕分けして、Claude Codeがそのまま動ける指示書に変える朝の10分の型を紹介します。