はじめに
AWS SAM(Serverless Application Model)は、API Gateway + Lambda + DynamoDB + S3 などを簡単に構築できる便利なフレームワークです。
この記事では、S3 ホスティング × Lambda × API Gateway × DynamoDB を連携させた構成を、AWS SAM を使って一括デプロイし、API 経由で DynamoDB のデータを取得・表示するまでの実践手順をまとめています。
※内容に不備などがございましたら、お手数ですが優しくご指摘いただけますと幸いです。
書こうと思ったきっかけ
個人的に AWS SAM をキャッチアップしたくて勉強を進めていたのですが、やってみたかった検証が一通りまとまったので、整理してみました。
1本目の記事では、シンプルな Hello World チュートリアルを実施した内容をまとめています。
興味のある方はぜひご覧ください!
1本目はこんな感じの検証内容です
2本目の記事では、API 経由で DynamoDB に保存されたテストデータを取得し、Lambda 関数からレスポンスとして返す構成を検証しています。
こちらもぜひご参考ください!
2本目はこんな感じの検証内容です
引用元:https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/http-api-dynamo-db.html
今回の検証内容
以下のようなシンプルかつ実用的なサーバーレス構成になります:
+-------------------+
| Web Browser |
+-------------------+
│
▼
静的Web(HTML/JS) ← S3でホスティング
│
▼
JavaScript → API呼び出し(fetchなど)
│
▼
API Gateway → Lambda → DynamoDB
構成図はこんな感じになります
技術構成の詳細
番号 | 構成要素 | 説明 |
---|---|---|
① | S3 | 静的Webサイト(HTML / CSS / JS)をホスティング |
② | JavaScript | API Gateway のエンドポイントを呼び出すコード |
③ | API Gateway + Lambda + DynamoDB | バックエンド処理(データ取得) |
SAM で構築する範囲
- Lambda 関数
- DynamoDB テーブル
- API Gateway エンドポイント
- S3 バケット(静的ホスティング)
ファイル構成と内容
ディレクトリ構成
sam-s3-api-example/
├── template.yaml # SAM テンプレート(インフラ定義)
├── samconfig.toml # デプロイ設定ファイル(--guided の保存内容)
├── hello_world/
│ ├── app.py # Lambda 関数(DynamoDB スキャン)
│ └── __init__.py # 空で OK(Python モジュール認識)
└── frontend/
└── index.html # 静的ホスティングされる HTML
template.yaml(インフラ全体定義)
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: API Gateway + Lambda + DynamoDB + S3 Static Web Hosting
Globals:
Function:
Timeout: 10
Resources:
WebHostingBucket:
Type: AWS::S3::Bucket
Properties:
WebsiteConfiguration:
IndexDocument: index.html
OwnershipControls:
Rules:
- ObjectOwnership: ObjectWriter
PublicAccessBlockConfiguration:
BlockPublicAcls: false
BlockPublicPolicy: false
IgnorePublicAcls: false
RestrictPublicBuckets: false
BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref WebHostingBucket
PolicyDocument:
Statement:
- Action: s3:GetObject
Effect: Allow
Resource: !Sub "${WebHostingBucket.Arn}/*"
Principal: "*"
ItemsTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ItemsTable
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
BillingMode: PAY_PER_REQUEST
MyApi:
Type: AWS::Serverless::Api
Properties:
Name: MyApi
StageName: Prod
Cors:
AllowMethods: "'GET,OPTIONS'"
AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'"
AllowOrigin: "'*'"
GetItemsFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.12
Environment:
Variables:
TABLE_NAME: !Ref ItemsTable
Policies:
- DynamoDBReadPolicy:
TableName: !Ref ItemsTable
Events:
GetItemsApi:
Type: Api
Properties:
RestApiId: !Ref MyApi
Path: /items
Method: get
Outputs:
WebURL:
Value: !GetAtt WebHostingBucket.WebsiteURL
ApiURL:
Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/items"
コードの中でやっていること
hello_world/app.py(Lambda関数)
import os
import boto3
import json
from decimal import Decimal
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ['TABLE_NAME'])
# Decimal を float/int に変換できるようにするヘルパー関数
class DecimalEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, Decimal):
# 整数化できる場合は int、それ以外は float
return int(o) if o % 1 == 0 else float(o)
return super(DecimalEncoder, self).default(o)
def lambda_handler(event, context):
print("=== Lambda triggered ===")
try:
response = table.scan()
print("Scan result:", response)
items = response.get('Items', [])
return {
'statusCode': 200,
'headers': {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Content-Type",
"Access-Control-Allow-Methods": "GET,OPTIONS",
"Content-Type": "application/json"
},
'body': json.dumps(items, cls=DecimalEncoder)
}
except Exception as e:
print("Error occurred:", str(e))
return {
'statusCode': 500,
'headers': {
"Access-Control-Allow-Origin": "*"
},
'body': json.dumps({'error': str(e)})
}
コードの中でやっていること
-
DynamoDB のデータを全件取得し、JSON 形式でレスポンスとして返す
- Lambda 関数内で
scan()
を使用して DynamoDB の全データを取得 - クライアントには JSON 形式で整形されたレスポンス を返す
- Lambda 関数内で
-
API Gateway 経由で呼び出され、CORS に対応
- フロントエンド(S3)からのリクエストを許可するため、CORS ヘッダーを付与
- 例:
Access-Control-Allow-Origin: *
をレスポンスヘッダーに追加
-
Decimal 型(DynamoDB 特有)を Python の int や float に変換してから返す
- DynamoDB の
scan()
結果にはDecimal
型が含まれるため、
Python のint
/float
に変換する処理を追加(例:カスタム JSON エンコーダーの実装)
- DynamoDB の
frontend/index.html(静的Web)
lang="ja">
charset="UTF-8">
Item Viewer
Items from DynamoDB
fetch('https://YOUR_API_ID.ap-northeast-1.amazonaws.com/Prod/items')
.then(res => res.json())
.then(jsonString => {
const data = typeof jsonString === "string" ? JSON.parse(jsonString) : jsonString;
const ul = document.getElementById('items');
data.forEach(item => {
const li = document.createElement('li');
li.textContent = `${item.name}(${item.id}): ¥${item.price}`;
ul.appendChild(li);
});
})
.catch(err => {
console.error("Fetch error:", err);
});
※ YOUR_API_ID
は実際のデプロイ後に表示される API URL に置き換えてください。
コードの中でやっていること
-
API Gateway のエンドポイントにアクセス
-
fetch('https://YOUR_API_ID.ap-northeast-1.amazonaws.com/Prod/items')
で、Lambda 経由で DynamoDB のデータを取得
-
-
取得した JSON データを処理
- レスポンスが 文字列形式かオブジェクト形式かをチェック し、安全にパース
- 各アイテムをループで処理
-
HTML の
要素にリスト表示
- 取得したデータを元に
-
に追加
-
表示形式の例:
ホンダ(car1): ¥150
- 取得したデータを元に
-
エラー時の処理
-
fetch()
に失敗した場合は、コンソールにエラーメッセージを出力
-
実際に AWS 環境上にデプロイしてみた
SAM テンプレートをビルド
問題なくビルドされていることが確認できました!
対話形式でデプロイ
例)対話での入力内容:
Stack Name [sam-app]: sam-s3-api-example
AWS Region [ap-northeast-1]: ap-northeast-1
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [y/N]: n
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: y
#Preserves the state of previously provisioned resources when an operation fails
Disable rollback [y/N]: n
GetItemsFunction has no authentication. Is this okay? [y/N]: y
Save arguments to configuration file [Y/n]: y
SAM configuration file [samconfig.toml]:
SAM configuration environment [default]:
表示された出力(Outputs)にある WebURL と ApiURL を控えておく
ここが大事!!(マネコン上でも確認できますが…!)
frontend/index.html
の API URL を修正してビルドディレクトリへコピー
HTML ファイルを S3 バケットへアップロード
aws s3 cp frontend/index.html s3://your-bucket-name/
こんな感じS3バケットに正常にアップロードされていることが確認できました!
DynamoDBの動作確認用のテストデータとして、以下のコマンドを実行してください!
aws dynamodb batch-write-item --request-items '{
"ItemsTable": [
{
"PutRequest": {
"Item": {
"id": { "S": "car1" },
"name": { "S": "ホンダ" },
"price": { "N": "150" }
}
}
},
{
"PutRequest": {
"Item": {
"id": { "S": "car2" },
"name": { "S": "トヨタ" },
"price": { "N": "180" }
}
}
},
{
"PutRequest": {
"Item": {
"id": { "S": "car3" },
"name": { "S": "スズキ" },
"price": { "N": "130" }
}
}
}
]
}'
問題なくテストデータが投入されたことが、マネジメントコンソール上でも確認できました!
ブラウザで S3のオブジェクト URL にアクセスし、DynamoDB のデータが表示されることを確認
まとめ
ここまでお読みいただき、ありがとうございました!
API Gateway + Lambda + DynamoDB + S3 の組み合わせは SAM で簡単に構築でき感動しました…!
今回は GET 処理 に絞った構成でしたが、POST リクエスト や Cognito 認証 との連携など、さらに応用的な構成にも発展させることが可能なので挑戦してみたいです!
不要になったリソースは以下のコマンドで削除できますので、適宜削除しておくことをおすすめします。
aws cloudformation delete-stack --stack-name sam-app
今後も応用的な構成に対応できるよう、引き続きキャッチアップを進めていきたいと思います…!
参考文献
Source link
Views: 0