1.はじめに
1.1.構築背景
本記事では、S3(静的)画面にアクセスすると Ajaxにより APIGatewayを呼出して、Lambda が DynamoDBの情報を取得、 S3(静的)画面を変更するというハンズオンになります。
※ハンズオンについて
LLMに「S3静的ページを動的に取得するための構築ハンズオンつくって」といってベースを作成してもらい、実際にAWSCLIを利用して構築・挙動確認まで実施しております。
1.2.Ajaxとは?
「Asynchronous JavaScript and XML」の略で、Webページを再読み込みしないで、JavaScript使ってサーバーとデータやり取りする技術を指す。
なぜAPI Gatewayを呼び出せるのか?
AjaxはHTTPリクエスト(GET、POST等)を送信する機能を持っており、API GatewayはHTTPSエンドポイントとして公開されているため、通常のWebサーバーと同様にアクセス可能です。
ただし、異なるドメイン間の通信となるため、CORS(Cross-Origin Resource Sharing)設定が必要になります。
今回のケースでは、S3ドメインからAPI Gatewayドメインへの通信となるため、API Gateway側でCORS設定を行うことで、Ajaxからの呼び出しが可能となります。
1.3.システム構成
1.3.1.アクセスフロー
| 項番 | 挙動の説明 | 
|---|---|
| 1 | ユーザーがCloudFront経由でS3のWebページにアクセス | 
| 2 | WebページからAjaxでAPI Gatewayに非同期リクエスト | 
| 3 | API GatewayがLambda関数を実行 | 
| 4 | LambdaがDynamoDBから最新ニュース情報を取得 | 
| 5 | 取得したデータをWebページに表示 | 
1.3.2.システム構成図
1.4.利用AWSリソース
| 項番 | AWSサービス名 | 詳細 | 
|---|---|---|
| 1 | DynamoDB | データストレージとして、AWSの最新情報を保存 | 
| 2 | IAM | Lambdaの権限を管理 | 
| 3 | Lambda | APIGatewayからのアクセスによりDynamoDBから情報取得 | 
| 4 | API Gateway | ブラウザ(S3上のWebページ)からのAjaxリクエストを受け付けるエンドポイント | 
| 5 | S3 | Webホスティング用 | 
2.ハンズオン
2.1.前提
AWS CLIを利用して構築をおこなう。
2.1.1.実行環境
| 環境 | 設定 | 
|---|---|
| 環境 | AWS CloudShell | 
2.1.2.DynamoDB作成
1.設定変数
# 設定変数
TABLE_NAME="sample-list-tables"
REGION="us-east-1"
PROJECT_TAG="news-fetcher"
ENVIRONMENT_TAG="demo"
2.DynamoDB作成
- 
--billing-modeの課金については、リクエスト量に応じた従量課金モード(PAY_PER_REQUEST)です。PoCのためアクセス量が少なくコストを抑えられるためとなります。 
# テーブル作成
aws dynamodb create-table \
    --table-name $TABLE_NAME \
    --attribute-definitions AttributeName=id,AttributeType=S \
    --key-schema AttributeName=id,KeyType=HASH \
    --billing-mode PAY_PER_REQUEST \
    --region $REGION \
    --tags \
        Key=Project,Value=$PROJECT_TAG \
        Key=Environment,Value=$ENVIRONMENT_TAG
3.テーブル確認
# テーブル確認
aws dynamodb describe-table --table-name $TABLE_NAME --region $REGION
4.サンプルデータ作成
aws dynamodb put-item \
    --table-name $TABLE_NAME \
    --region $REGION \
    --item '{
        "id": {"S": "aws-news-001"},
        "title": {"S": "Amazon S3 Express One Zone Storage Class Available"},
        "link": {"S": "https://aws.amazon.com/jp/blogs/news/amazon-s3-express-one-zone-high-performance-storage-class/"},
        "published": {"S": "2025-07-20T12:00:00+09:00"},
        "timestamp": {"N": "1721530800"}
    }'
aws dynamodb put-item \
    --table-name $TABLE_NAME \
    --region $REGION \
    --item '{
        "id": {"S": "aws-news-002"},
        "title": {"S": "AWS Lambda Python 3.12 Runtime Support"},
        "link": {"S": "https://aws.amazon.com/jp/blogs/compute/python-3-12-runtime-now-available-in-aws-lambda/"},
        "published": {"S": "2025-07-19T10:30:00+09:00"},
        "timestamp": {"N": "1721444200"}
    }'
aws dynamodb put-item \
    --table-name $TABLE_NAME \
    --region $REGION \
    --item '{
        "id": {"S": "aws-news-003"},
        "title": {"S": "Amazon EC2 M7i Instances Available in Tokyo Region"},
        "link": {"S": "https://aws.amazon.com/jp/blogs/news/new-amazon-ec2-m7i-instances-powered-by-custom-intel-xeon-scalable-processors/"},
        "published": {"S": "2025-07-18T14:15:00+09:00"},
        "timestamp": {"N": "1721357700"}
    }'
5.サンプルデータ挿入確認
# テーブル確認
aws dynamodb scan --table-name $TABLE_NAME --region $REGION --query 'Items[*].[id.S, title.S]' --output table
# レスポンス
------------------------------------------------------------------------
|                                 Scan                                 |
+--------------+-------------------------------------------------------+
|  aws-news-002|  AWS Lambda Python 3.12 Runtime Support               |
|  aws-news-001|  Amazon S3 Express One Zone Storage Class Available   |
|  aws-news-003|  Amazon EC2 M7i Instances Available in Tokyo Region   |
+--------------+-------------------------------------------------------+
2.1.3.IAM作成
1.設定変数
# 設定変数
ROLE_NAME="sample-lambda-dynamodb-role"
POLICY_NAME="sample-lambda-dynamodb-policy"
REGION=$(aws configure get region)
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
2.信頼 Policyの内容作成
CloudShell(ローカル)に 信頼policy を記載した jsonファイルを作成し、そちらをIAM Role作成時に指定して実行# IAM Policy作成
cat > trust-policy.json << EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "lambda.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
EOF
3.IAM Role作成
# IAM Role作成
aws iam create-role \
    --role-name $ROLE_NAME \
    --assume-role-policy-document file://trust-policy.json \
    --description "PoC Lambda execution role with DynamoDB read access"
4.IAM Role へ IAM Policy アタッチ
# IAM Policyアタッチ
# 1. Lambda基本実行権限
aws iam attach-role-policy \
    --role-name $ROLE_NAME \
    --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
# 2. DynamoDB読み取り権限
aws iam attach-role-policy \
    --role-name $ROLE_NAME \
    --policy-arn arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess
5.IAM Role の ARN取得
# IAM Role の ARN取得
ROLE_ARN=$(aws iam get-role --role-name $ROLE_NAME --query 'Role.Arn' --output text)
echo "IAMロールARN: $ROLE_ARN"
2.1.4.Lambda作成
1.設定変数
# Lambda関数用の変数追加
FUNCTION_NAME="dynamodb-news-api"
RUNTIME="python3.12"
HANDLER="get-dynamodb-data.lambda_handler"
2.Lambda コードファイル作成
- DynamoDBを読み込む設定は 右記設定です 
response = table.scan(Limit=limit)。scanを利用することでテーブル全体を読み込む操作をしています。データ量が大きい場合はqueryに変えるなどの検討をお願いします。 
| 項番 | 関数種類 | 詳細 | 
|---|---|---|
| 1 | ヘルパー関数 | decimal_to_serializable関数 | 
| 2 | ヘルパー関数 | convert_dynamodb_item関数 | 
| 3 | ヘルパー関数 | create_cors_headers関数 | 
| 4 | ヘルパー関数 | create_response関数 | 
| 5 | メイン処理 | lambda_handler関数 | 
# コードファイル作成
cat > get-dynamodb-data.py << 'EOF'
import json
import boto3
import os
from boto3.dynamodb.conditions import Key
from decimal import Decimal
from typing import Dict, Any, List
import logging
# ログ設定
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# 環境変数
TABLE_NAME = os.environ.get('TABLE_NAME', 'default-table')
def decimal_to_serializable(obj: Any) -> Any:
    """Decimal型をJSON serializableな型に変換"""
    if isinstance(obj, Decimal):
        if obj % 1 == 0:
            return int(obj)
        return float(obj)
    return obj
def convert_dynamodb_item(item: Dict[str, Any]) -> Dict[str, Any]:
    """DynamoDBアイテムをJSON serializable形式に変換"""
    converted_item = {}
    for key, value in item.items():
        converted_item[key] = decimal_to_serializable(value)
    return converted_item
def create_cors_headers() -> Dict[str, str]:
    """CORS用ヘッダー生成"""
    return {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type, X-Amz-Date, Authorization, X-Api-Key, X-Amz-Security-Token'
    }
def create_response(status_code: int, body: Dict[str, Any]) -> Dict[str, Any]:
    """API Gateway用レスポンス生成"""
    return {
        'statusCode': status_code,
        'headers': create_cors_headers(),
        'body': json.dumps(body, ensure_ascii=False)
    }
def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
    """Lambda関数メインハンドラー"""
    try:
        logger.info(f"Received event: {json.dumps(event, default=str)}")
        
        # OPTIONSリクエストの場合はCORSレスポンス
        if event.get('httpMethod') == 'OPTIONS':
            return create_response(200, {'message': 'CORS preflight'})
        
        # DynamoDBクライアント作成
        dynamodb = boto3.resource('dynamodb')
        table = dynamodb.Table(TABLE_NAME)
        
        # クエリパラメータから取得件数決定(デフォルト10件、最大50件)
        query_params = event.get('queryStringParameters') or {}
        limit = min(int(query_params.get('limit', 10)), 50)
        
        logger.info(f"Scanning table with limit: {limit}")
        
        # データ取得
        response = table.scan(Limit=limit)
        
        if 'Items' not in response:
            logger.warning("No items found in response")
            return create_response(200, {
                'success': True,
                'data': [],
                'count': 0,
                'message': 'データが見つかりませんでした'
            })
        
        # データ変換
        items = [convert_dynamodb_item(item) for item in response['Items']]
        
        # timestampでソート(降順 = 最新順)
        items.sort(key=lambda x: x.get('timestamp', 0), reverse=True)
        
        logger.info(f"Successfully retrieved {len(items)} items")
        
        return create_response(200, {
            'success': True,
            'data': items,
            'count': len(items),
            'message': f'{len(items)}件のデータを取得しました'
        })
        
    except Exception as e:
        error_message = f"エラーが発生しました: {str(e)}"
        logger.error(error_message, exc_info=True)
        
        return create_response(500, {
            'success': False,
            'error': error_message,
            'data': [],
            'count': 0
        })
EOF
3.デプロイ用zipファイル作成
# デプロイファイル作成
zip get-dynamodb-data.zip get-dynamodb-data.py
4.Lambda関数 デプロイ
# デプロイファイル作成
aws lambda create-function \
    --function-name $FUNCTION_NAME \
    --runtime $RUNTIME \
    --role $ROLE_ARN \
    --handler $HANDLER \
    --zip-file fileb://get-dynamodb-data.zip \
    --timeout 30 \
    --memory-size 256 \
    --region $REGION \
    --environment "Variables={TABLE_NAME=$TABLE_NAME}" \
    --description "News fetcher API for DynamoDB"
5.Lambda関数 構築確認
# Lambda関数 構築確認
aws lambda get-function --function-name $FUNCTION_NAME --region $REGION
6.Lambda関数 テスト
DynamoDBにサンプルで保存した情報を取得することが可能である# Lambdaテスト
aws lambda invoke \
    --function-name $FUNCTION_NAME \
    --region $REGION \
    --payload '{}' \
    response.json
# レスポンス確認
cat response.json
2.1.5.APIGateway作成
1.設定変数
# 設定変数
API_NAME="sample-dynamodb-news-api"
API_DESCRIPTION="News fetcher API for DynamoDB demo"
STAGE_NAME="prod"
2.API Gateway作成
API Gatewayのリソースを作成し、リソースに紐づくIDを取得# API Gateway作成
API_ID=$(aws apigateway create-rest-api \
    --name "$API_NAME" \
    --description "$API_DESCRIPTION" \
    --query 'id' --output text)
# API ID確認
echo $API_ID
3./data リソース作成
- API Gatewayのルートリソースを取得して、新規のパス
/dataを作成 
# ルートID取得
ROOT_ID=$(aws apigateway get-resources \
    --rest-api-id $API_ID \
    --query 'items[0].id' --output text)
# ルートID確認
echo $ROOT_ID
# /data 作成
RESOURCE_ID=$(aws apigateway create-resource \
    --rest-api-id $API_ID \
    --parent-id $ROOT_ID \
    --path-part "data" \
    --query 'id' --output text)
# /dataのID確認
echo $RESOURCE_ID
4.Getメソッド作成
- 
/dataリソースに GETメソッドを追加します。 
# /data に GETメソッド作成
aws apigateway put-method \
    --rest-api-id $API_ID \
    --resource-id $RESOURCE_ID \
    --http-method GET \
    --authorization-type NONE
# Lambda統合設定
aws apigateway put-integration \
    --rest-api-id $API_ID \
    --resource-id $RESOURCE_ID \
    --http-method GET \
    --type AWS_PROXY \
    --integration-http-method POST \
    --uri "arn:aws:apigateway:$REGION:lambda:path/2015-03-31/functions/arn:aws:lambda:$REGION:$ACCOUNT_ID:function:$FUNCTION_NAME/invocations"
5.Lambda実行権限追加
Lambdaに APIGatewayから呼出しを許可(リソースベースのポリシーステートメント)# APIGatewayからの呼出しを許可
aws lambda add-permission \
    --function-name $FUNCTION_NAME \
    --statement-id apigateway-invoke-$(date +%s) \
    --action lambda:InvokeFunction \
    --principal apigateway.amazonaws.com \
    --source-arn "arn:aws:execute-api:$REGION:$ACCOUNT_ID:$API_ID/*/*" \
    --region $REGION
6.CORS設定
CORS(Cross-Origin Resource Sharing)とは、ブラウザのセキュリティ制限を回避する設定を指す。 ブラウザは異なるドメイン間のAjax通信を自動的にブロックするため、S3のWebサイト(example.com)からAPI Gateway(amazonaws.com)を呼び出すと「Same-Origin Policy」に引っかかってエラーになってしまう。そのため以下の設定おこなう。
| 項番 | 項目名 | 詳細 | 
|---|---|---|
| 1 | OPTIONSメソッド作成 | アクセスがあった際 事前にOPTIONSメソッドが呼ばれ「ブロックしなくてよい」として実際の通信を許可す設定 | 
| 2 | MOCK統合設定 | OPTIONSメソッド自体はレスポンスのみが必要となり、実際のLambda等を利用した処理はないため ダミー処理とするため設定 | 
| 3 | OPTIONSメソッドレスポンス設定 | アクセスがあった際に レスポンスするヘッダーの設定(設定値は、4.統合レスポンス設定で入力) | 
| 4 | 統合レスポンス設定 | レスポンスするヘッダーの具体的な値を設定 | 
今回利用している OPTIONSメソッドレスポンスの項目
| 項番 | ヘッダー名 | 設定値 | 設定値の説明 | 
|---|---|---|---|
| 1 | Access-Control-Allow-Headers | Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token | 左記ヘッダーの使用を許可 | 
| 2 | Access-Control-Allow-Method | GET, OPTIONS | 左記2つのメソッドのみを許可 | 
| 3 | Access-Control-Allow-Origin | * | 左記ドメインからのアクセスを許可(PoCのため、どのドメインからでもアクセス可能) | 
※ Access-Control-Allow-Originに関して、本番利用の場合はセキュリティ強化のために特定ドメインに変更ください
# 1. OPTIONSメソッド作成
aws apigateway put-method \
    --rest-api-id $API_ID \
    --resource-id $RESOURCE_ID \
    --http-method OPTIONS \
    --authorization-type NONE
# 2.MOCK統合設定
aws apigateway put-integration \
    --rest-api-id $API_ID \
    --resource-id $RESOURCE_ID \
    --http-method OPTIONS \
    --type MOCK \
    --request-templates '{"application/json":"{\"statusCode\":200}"}'
# 3.OPTIONSメソッドレスポンス設定
aws apigateway put-method-response \
    --rest-api-id $API_ID \
    --resource-id $RESOURCE_ID \
    --http-method OPTIONS \
    --status-code 200 \
    --response-parameters "method.response.header.Access-Control-Allow-Headers=false,method.response.header.Access-Control-Allow-Methods=false,method.response.header.Access-Control-Allow-Origin=false"
# 4.統合レスポンス設定
aws apigateway put-integration-response \
    --rest-api-id $API_ID \
    --resource-id $RESOURCE_ID \
    --http-method OPTIONS \
    --status-code 200 \
    --response-parameters '{"method.response.header.Access-Control-Allow-Headers":"'"'"'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"'"'","method.response.header.Access-Control-Allow-Methods":"'"'"'GET,OPTIONS'"'"'","method.response.header.Access-Control-Allow-Origin":"'"'"'*'"'"'"}'
7.APIGatewayのデプロイ・テスト
# APIGatewayのデプロイ
aws apigateway create-deployment \
    --rest-api-id $API_ID \
    --stage-name $STAGE_NAME
# APIGateway URL確認
API_ENDPOINT="https://$API_ID.execute-api.$REGION.amazonaws.com/$STAGE_NAME/data"
echo "$API_ENDPOINT"
# APIテスト
curl "$API_ENDPOINT"
2.1.6.S3静的ホスティング設定
1.設定変数
# 設定変数
BUCKET_NAME="aws-news-fetcher-$(date +%s)"
2.S3バケット作成
# S3バケット作成
aws s3 mb s3://$BUCKET_NAME --region $REGION
3.CloudFront用 OAC作成
# OAC作成
OAC_ID=$(aws cloudfront create-origin-access-control \
    --origin-access-control-config '{
        "Name": "OAC-'$BUCKET_NAME'",
        "Description": "OAC for '$BUCKET_NAME'",
        "OriginAccessControlOriginType": "s3",
        "SigningBehavior": "always",
        "SigningProtocol": "sigv4"
    }' \
    --query 'OriginAccessControl.Id' --output text)
4.CloudFrontディストリビューション設定作成
# ディストリビューション設定値
cat > cloudfront-config.json << EOF
{
    "CallerReference": "cf-$(date +%s)",
    "Comment": "CloudFront for news fetcher demo",
    "DefaultRootObject": "index.html",
    "Origins": {
        "Quantity": 1,
        "Items": [
            {
                "Id": "$BUCKET_NAME-origin",
                "DomainName": "$BUCKET_NAME.s3.$REGION.amazonaws.com",
                "S3OriginConfig": {
                    "OriginAccessIdentity": ""
                },
                "OriginAccessControlId": "$OAC_ID"
            }
        ]
    },
    "DefaultCacheBehavior": {
        "TargetOriginId": "$BUCKET_NAME-origin",
        "ViewerProtocolPolicy": "redirect-to-https",
        "TrustedSigners": {
            "Enabled": false,
            "Quantity": 0
        },
        "ForwardedValues": {
            "QueryString": false,
            "Cookies": {
                "Forward": "none"
            }
        },
        "MinTTL": 0
    },
    "Enabled": true
}
EOF
5.CloudFront ディストリビューション作成
# CloudFrontディストリビューション作成
DISTRIBUTION_ID=$(aws cloudfront create-distribution \
    --distribution-config file://cloudfront-config.json \
    --query 'Distribution.Id' --output text)
# ドメイン名取得
CLOUDFRONT_DOMAIN=$(aws cloudfront get-distribution \
    --id $DISTRIBUTION_ID \
    --query 'Distribution.DomainName' --output text)
echo "https://$CLOUDFRONT_DOMAIN"
6.S3バケットポリシー設定(CloudFrontからのアクセス許可)
# バケットポリシー作成
cat > s3-policy-cloudfront.json << EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudfront.amazonaws.com"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::$BUCKET_NAME/*",
            "Condition": {
                "StringEquals": {
                    "AWS:SourceArn": "arn:aws:cloudfront::$(aws sts get-caller-identity --query Account --output text):distribution/$DISTRIBUTION_ID"
                }
            }
        }
    ]
}
EOF
# S3バケットポリシー更新
aws s3api put-bucket-policy \
    --bucket $BUCKET_NAME \
    --policy file://s3-policy-cloudfront.json
2.1.7.HTMLファイル作成
1.HTMLファイル作成
# HTMLファイル作成
cat > index.html << EOF
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AWS News Fetcher</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
        }
        .container {
            background: white;
            border-radius: 12px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
            overflow: hidden;
        }
        .header {
            background: linear-gradient(135deg, #ff6600 0%, #ff8800 100%);
            color: white;
            text-align: center;
            padding: 30px 20px;
        }
        .header h1 {
            margin: 0 0 10px 0;
            font-size: 2.5em;
            font-weight: 300;
        }
        .content {
            padding: 30px;
        }
        .loading {
            text-align: center;
            color: #666;
            font-style: italic;
            padding: 40px;
        }
        .error {
            background: #ffebee;
            color: #c62828;
            padding: 20px;
            border-radius: 8px;
            margin-bottom: 20px;
        }
        .news-container {
            display: grid;
            gap: 20px;
        }
        .news-item {
            background: white;
            padding: 25px;
            border-radius: 12px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.1);
            border-left: 4px solid #ff6600;
        }
        .news-title {
            font-size: 20px;
            font-weight: 600;
            margin-bottom: 12px;
        }
        .news-title a {
            color: #0066cc;
            text-decoration: none;
        }
        .news-title a:hover {
            color: #ff6600;
            text-decoration: underline;
        }
        .news-meta {
            color: #666;
            font-size: 14px;
            margin-bottom: 10px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>AWS News Fetcher</h1>
            <p>DynamoDB → Lambda → API Gateway → CloudFront → S3</p>
        </div>
        <div class="content">
            <div id="loading" class="loading">データを取得中...</div>
            <div id="error" class="error" style="display: none;"></div>
            <div id="news-container" class="news-container"></div>
        </div>
    </div>
    <script>
        const API_URL = '$API_ENDPOINT';
        document.addEventListener('DOMContentLoaded', function() {
            fetchNews();
        });
        function fetchNews() {
            const loadingDiv = document.getElementById('loading');
            const errorDiv = document.getElementById('error');
            const newsContainer = document.getElementById('news-container');
            fetch(API_URL)
                .then(response => {
                    if (!response.ok) {
                        throw new Error('HTTP error! status: ' + response.status);
                    }
                    return response.json();
                })
                .then(data => {
                    loadingDiv.style.display = 'none';
                    if (data.success && data.data && data.data.length > 0) {
                        displayNews(data.data);
                    } else {
                        showError('データが見つかりませんでした');
                    }
                })
                .catch(error => {
                    loadingDiv.style.display = 'none';
                    showError('エラーが発生しました: ' + error.message);
                    console.error('Error:', error);
                });
        }
        function displayNews(newsData) {
            const newsContainer = document.getElementById('news-container');
            
            newsData.forEach(function(item) {
                const newsItem = document.createElement('div');
                newsItem.className = 'news-item';
                
                const publishedDate = new Date(item.published).toLocaleString('ja-JP');
                
                newsItem.innerHTML = 
                    '<div class="news-title">' +
                        '<a href="' + item.link + '" target="_blank" rel="noopener noreferrer">' + item.title + '</a>' +
                    '</div>' +
                    '<div class="news-meta">' +
                        '公開日: ' + publishedDate +
                    '</div>';
                
                newsContainer.appendChild(newsItem);
            });
        }
        function showError(message) {
            const errorDiv = document.getElementById('error');
            errorDiv.textContent = message;
            errorDiv.style.display = 'block';
        }
    </script>
</body>
</html>
EOF
2.S3にアップロード
#S3アップロード
aws s3 cp index.html s3://$BUCKET_NAME/
3.挙動確認
3.1.コマンドによる確認
# コマンドによるアクセス
curl -I https://$CLOUDFRONT_DOMAIN
# レスポンス
HTTP/2 200 
content-type: text/html
content-length: 5018
date: Sun, 20 Jul 2025 01:18:13 GMT
last-modified: Sun, 20 Jul 2025 01:14:59 GMT
etag: "40a8b6c06b76bd2f479a825a057d0e71"
x-amz-server-side-encryption: AES256
accept-ranges: bytes
server: AmazonS3
x-cache: Miss from cloudfront
via: 1.1 9c13d38452f2017c92d65c8904408686.cloudfront.net (CloudFront)
x-amz-cf-pop: IAD55-P2
x-amz-cf-id: X7gI-7u86sI346-jclwmfUhm0Z4T9q8wl70PHNYokZKI4TZaXwzi-A==
3.2.ブラウザでの確認
4.おわりに
4.1.得られた知見
- Ajaxを利用した S3からのAPIGateway呼出し
 
4.2.今後の課題
- APIGatewayを呼出せるため、Bedrock等の呼出しにも挑戦してみる