Amazon API Gateway → Step Functions→ DynamoDBという構成で、データの新規登録や取得処理を試してみた
はじめに
Amazon API Gateway、AWS Step Functions、Amazon DynamoDBを組み合わせたサーバーレス構成で、データの新規登録や取得処理を試しました。
通常、API GatewayのバックエンドにはAWS Lambdaを採用することが多いですが、今回はLambdaを使用せず、Step Functionsを直接呼び出す構成としています。この構成にはLambdaとは異なる設定上の注意点がいくつか存在するため、その解決策を含めて解説します。
なお、本記事では設定方法の解説に重点を置いているため、レストラン予約システムを題材としていますが、データベース設計などは検証用に簡略化しています。
DynamoDBテーブル作成
AWS CloudShellを開き、以下のコマンドを実行してDynamoDBテーブルを作成します。
$ aws dynamodb create-table \
--table-name RestaurantReservations \
--attribute-definitions \
AttributeName=PhoneNumber,AttributeType=S \
AttributeName=ReservationTime,AttributeType=S \
--key-schema \
AttributeName=PhoneNumber,KeyType=HASH \
AttributeName=ReservationTime,KeyType=RANGE \
--billing-mode PAY_PER_REQUEST
Step Functions の作成
ステートマシンのタイプは Express を選択して作成します。

API GatewayからStep Functionsを同期的に呼び出し、結果を受け取るためには、StandardタイプではなくExpressタイプが必須となるためです。
同期 Express ワークフローはワークフローを開始して、それが完了するまで待機してから、結果を返します。同期 Express ワークフローはマイクロサービスのオーケストレーションに使用できます。同期 Express ワークフローを使用すると、エラー処理、再試行、並列タスクの実行のための追加のコードを開発しなくても、アプリケーションを開発できます。Amazon API Gateway から呼び出された同期 Express ワークフローを実行するかAWS Lambda、 StartSyncExecution API コールを使用して実行できます。
https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/choosing-workflow-type.html
ステートマシンのワークフロー定義(ASL)は以下の通りです。

{
"Comment": "レストラン予約フロー",
"QueryLanguage": "JSONata",
"StartAt": "RouteRequest",
"States": {
"RouteRequest": {
"Type": "Choice",
"Choices": [
{
"Condition": "{% $states.input.action = 'create' %}",
"Next": "CreateReservation"
},
{
"Condition": "{% $states.input.action = 'check' %}",
"Next": "CheckReservation"
}
],
"Default": "InvalidAction"
},
"CreateReservation": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:dynamodb:putItem",
"Arguments": {
"TableName": "RestaurantReservations",
"Item": {
"PhoneNumber": {
"S": "{% $states.input.phone %}"
},
"ReservationTime": {
"S": "{% $states.input.time %}"
},
"CustomerName": {
"S": "{% $states.input.name %}"
},
"PeopleCount": {
"N": "{% $string($states.input.count) %}"
},
"Status": {
"S": "RESERVED"
}
},
"ConditionExpression": "attribute_not_exists(PhoneNumber)"
},
"Output": {
"status": "success",
"message": "ご予約を承りました。"
},
"Catch": [
{
"ErrorEquals": [
"DynamoDB.ConditionalCheckFailedException"
],
"Next": "AlreadyExists"
}
],
"End": true
},
"CheckReservation": {
"Type": "Task",
"Resource": "arn:aws:states:::aws-sdk:dynamodb:query",
"Arguments": {
"TableName": "RestaurantReservations",
"KeyConditionExpression": "PhoneNumber = :p AND ReservationTime >= :now",
"ExpressionAttributeValues": {
":p": {
"S": "{% $states.input.phone %}"
},
":now": {
"S": "{% $now() %}"
}
}
},
"Output": {
"status": "success",
"found": "{% $count($states.result.Items) > 0 %}",
"reservations": "{% $states.result.Items.{ 'name': CustomerName.S, 'time': ReservationTime.S, 'count': PeopleCount.N, 'status': Status.S } %}",
"message": "{% $count($states.result.Items) > 0 ? 'これからのご予約が見つかりました。' : '現在、これからのご予約は承っておりません。' %}"
},
"End": true
},
"AlreadyExists": {
"Type": "Pass",
"Output": {
"status": "error",
"message": "お客様は既に同じ日時でご予約されています。重複予約はできません。"
},
"End": true
},
"InvalidAction": {
"Type": "Pass",
"Output": {
"status": "error",
"message": "不正な操作です。"
},
"End": true
}
}
}
このステートマシンの処理内容は以下の通りです。
- RouteRequest: 入力された
actionパラメータの値(createまたはcheck)に基づいて処理を分岐させます。 - CreateReservation: 新規予約の場合、
dynamodb:putItemを使用してデータを登録します。この際、同じ電話番号のデータが存在しないことを条件としています。 - CheckReservation: 予約確認の場合、
dynamodb:queryを使用して、指定された電話番号かつ現在時刻以降の予約データを検索します。JSONataを使用して、検索結果を整形して返します。
作成時、IAMロールと一部のIAMポリシーは自動作成されますが、一部、DynamoDBへの操作権限が不足しているため、今回は検証用として AmazonDynamoDBFullAccess_v2 を追加します。なお、本記事では検証をスムーズに行うためFullAccessポリシーを使用していますが、本番環境ではセキュリティの観点から、必要なリソースとアクションのみを許可した最小権限のポリシーを設定することを推奨します。

API Gateway用IAMロールの作成
AWS CloudShellを開き、以下のコマンドを実行して、API GatewayがStep Functionsを実行するために必要なIAMロールを作成します。
# 信頼ポリシー作成
echo '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "apigateway.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}' > trust-policy.json
# ロール作成
aws iam create-role \
--role-name APIGatewayToStepFunctionsRole \
--assume-role-policy-document file://trust-policy.json
# 権限付与
aws iam attach-role-policy \
--role-name APIGatewayToStepFunctionsRole \
--policy-arn arn:aws:iam::aws:policy/AWSStepFunctionsFullAccess
# ARNの確認 (★これを控える)
aws iam get-role \
--role-name APIGatewayToStepFunctionsRole \
--query 'Role.Arn' \
--output text
API Gatewayの作成
以下の設定でAPI Gatewayを作成します。

予約確認API (GET) の作成
/reservations リソースを作成し、GETメソッドを追加します。
以下の設定でセットアップを行います。
- 統合タイプ: AWS サービス
- AWS リージョン: (利用するリージョン)
- AWS サービス: Step Functions
- HTTP メソッド: POST
- アクション名: StartSyncExecution
- 実行ロール: 先ほど作成したAPI Gateway用のIAMロールのARN
- URL クエリ文字列パラメータ:
phone(必須)

注記: API GatewayからStep Functionsを呼び出す場合、統合リクエストのHTTPメソッドは必ず POST を指定する必要があります。
注記
すべての Step Functions API アクションは HTTP POST メソッドを使用します。
https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/tutorial-api-gateway.html#api-gateway-step-2
ステートマシンの実行には同期実行可能な StartSyncExecution アクションを使用しますが、これはExpressタイプでのみ利用可能です。
Starts a Synchronous Express state machine execution. StartSyncExecution is not available for STANDARD workflows.
https://docs.aws.amazon.com/step-functions/latest/apireference/API_StartSyncExecution.html
作成後、「統合リクエスト」の設定を編集します。
- リクエスト本文のパススルー: 「テンプレートが定義されていない場合 (推奨)」を選択します。
- マッピングテンプレート:
- Content-Type:
application/json - テンプレート本文: 以下のコードを設定します(
YOUR_ACCOUNT_IDとステートマシン名は環境に合わせて書き換えてください)。
- Content-Type:
#set($phone = $input.params('phone'))
{
"stateMachineArn": "arn:aws:states:ap-northeast-1:YOUR_ACCOUNT_ID:stateMachine:RestaurantReservationFlow",
"input": "{\"action\": \"check\", \"phone\": \"$util.escapeJavaScript($phone)\"}"
}

URLクエリパラメータから電話番号を取得し、"action": "check" を付与してStep Functionsに渡すことで、予約確認処理を実行させます。
なお、マッピングテンプレートでは、以下のAWSドキュメント「例 3」などにも記載がある通り、入力値に予期せぬ文字が含まれていてもシステムが停止しない(400エラーにならない)ようにするため、またJSON構造を破壊されないために、変数を埋め込む際は $util.escapeJavaScript() を使用しています。
各設定項目について
-
リクエスト本文のパススルー
「テンプレートが定義されていない場合 (推奨)」を選択します。
この設定にすることで、クライアントから送信されたContent-Typeヘッダー(今回はapplication/json)に一致するマッピングテンプレートが存在する場合、必ずそのテンプレートを適用して変換を行うようになります。これにより、変換前の生データが誤ってバックエンドに送信されるのを防ぎます。 -
マッピングテンプレート
API Gateway(HTTPリクエスト)と Step Functions(AWS API)をつなぐための変換定義です。
Step Functions の同期実行 API (StartSyncExecution) は、リクエストボディとして以下の特定の JSON 構造を要求します。{ "stateMachineArn": "ステートマシンのARN", "input": "{\"key\": \"value\"}" // ※inputの中身はエスケープされたJSON文字列である必要がある }このテンプレートでは、URLクエリパラメータとして受け取った
phoneの値を抽出し、Step Functions が要求する上記のフォーマット(stateMachineArnとinput文字列)に変換して渡す処理を記述しています。
新規予約API (POST) の作成
同様に、/reservations リソースにPOSTメソッドを作成します。
- メソッドタイプ:POST
- 統合タイプ: AWSのサービス
- AWS サービス: Step Functions
- HTTP メソッド: POST
- アクション名: StartSyncExecution
- 実行ロール: 先程作成したAPI Gateway用のIAMロールのARN

作成後、「統合リクエスト」のマッピングテンプレートを以下のように設定します。
- Content-Type:
application/json - テンプレート本文: 以下のコードを設定します(
YOUR_ACCOUNT_IDとステートマシン名は環境に合わせて書き換えてください)。
{
"stateMachineArn": "arn:aws:states:ap-northeast-1:YOUR_ACCOUNT_ID:stateMachine:RestaurantReservationFlow",
"input": "$util.escapeJavaScript($input.json('$').replaceFirst('^\s*\{', '{ \"action\": \"create\", '))"
}
ここでは、クライアントから送信されたJSONデータに "action": "create" という項目を強制的に追加し、Step Functions側で処理を分岐できるように加工しています。
統合レスポンスの設定
Step Functionsの StartSyncExecution APIのレスポンスは、デフォルトでは実行ID(executionArn)などのメタデータを含んでおり、実際の処理結果は output フィールド内に文字列として格納されています。
このままではクライアント側で再度パース処理が必要になるため、実運用ではAPI Gatewayの「統合レスポンス」機能を使って output の中身を展開し、必要なデータのみを返却します。
以下のマッピングテンプレートを設定することで、不要なメタデータを除去し、整形されたJSONのみをレスポンスとして返却できます。
マッピングテンプレート (application/json)
#set($result = $input.path('$'))
#if($result.status == 'SUCCEEDED')
## 成功時は output の中身(JSON文字列)をそのまま展開してレスポンス本文にする
$result.output
#else
## 失敗時は500エラーとし、エラー内容を返す
#set($context.responseOverride.status = 500)
{
"error": "$result.error",
"cause": "$result.cause"
}
#end
POSTとGETメソッドどちらの統合レスポンスにも設定します。

デプロイ
設定が完了したらAPIをデプロイします。

※画面上部に This action is not available while the API is updating. と表示され、「APIのデプロイ」ボタンがクリックできない場合があります。その際はブラウザをリロードすると解消されることがあります。
「API のデプロイ」を選択し、ステージ名(例: dev)を指定してデプロイします。

動作確認
AWS CloudShellから curl コマンドを実行して動作を確認します。
新規予約 (POST)
curl -X POST "https://{api-id}.execute-api.ap-northeast-1.amazonaws.com/dev/reservations" \
-H "Content-Type: application/json" \
-d '{
"phone": "09011111111",
"time": "2026-01-14T12:00",
"name": "適当な名前",
"count": 2
}'
{
"status": "success",
"message": "ご予約を承りました。"
}
なお統合レスポンスを設定しない場合、以下になります。
{"billingDetails":{"billedDurationInMilliseconds":100,"billedMemoryUsedInMB":64},"executionArn":"arn:aws:states:ap-northeast-1:111111111111:express:RestaurantReservationFlow:4542c1e7-a5ce-46ba-80c4-78ca35d7730b:3168f3c7-436e-4154-8d76-3b39a3f0bc05","input":"{ \"action\": \"create\", \"phone\":\"09011111111\",\"time\":\"2026-01-14T12:00\",\"name\":\"適当な名前\",\"count\":2}","inputDetails":{"__type":"com.amazonaws.swf.base.model#CloudWatchEventsExecutionDataDetails","included":true},"name":"4542c1e7-a5ce-46ba-80c4-78ca35d7730b","output":"{\"status\":\"success\",\"message\":\"ご予約を承りました。\"}","outputDetails":{"__type":"com.amazonaws.swf.base.model#CloudWatchEventsExecutionDataDetails","included":true},"startDate":1.768268689381E9,"stateMachineArn":"arn:aws:states:ap-northeast-1:111111111111:stateMachine:RestaurantReservationFlow","status":"SUCCEEDED","stopDate":1.768268689451E9,"traceHeader":"Root=1-6965a391-ec956495d41a159d021c0498;Sampled=1"}
DynamoDBを確認すると、データが保存されていることがわかります。

予約確認 (GET)
curl "https://{api-id}.execute-api.ap-northeast-1.amazonaws.com/dev/reservations?phone=09011111111"
{
"status": "success",
"found": true,
"reservations": [
{
"name": "佐藤 次郎",
"time": "2026-01-14T12:00",
"count": "2",
"status": "RESERVED"
}
,
{
"name": "佐藤 次郎",
"time": "2026-01-16T12:00",
"count": "3",
"status": "RESERVED"
}
],
"message": "これからのご予約が見つかりました。"
}
最後に
本記事では、Lambdaを使用せずにAPI GatewayとStep Functionsを直接連携させる構成で、データの新規登録や取得処理を試しました。
この構成には、Lambdaを利用する場合とは異なる以下の注意点があります。
- Step Functionsのタイプ: 同期実行のために「Express」タイプを選択する必要がある
- API Gatewayの統合設定: Step FunctionsのAPIアクションはすべて「POST」メソッドで呼び出す必要がある
これらのポイントを押さえることで、Lambda関数のコード管理が不要になり、Step Functionsによる処理の流れを視覚的に把握しやすいサーバーレスアーキテクチャを構築できます。
参考







