AWS

AWS サーバーレス ハンズオン|Lambda・DynamoDB・API GatewayでCRUD APIを作る手順【2026年11月】

AWS サーバーレス ハンズオン|Lambda・DynamoDB・API GatewayでCRUD APIを作る手順【2026年11月】

AWS Lambda、Amazon DynamoDB、Amazon API Gatewayを組み合わせたサーバーレスAPIは、構成図こそ数年前と同じですが、足回りが変わりました。多くのハンズオンが開発環境に指定していたAWS Cloud9は新規受付を終了し、サンプルコードのpython3.8〜python3.10はすべて廃止済みです。2026年10月12日には従来のLambda関数が「Lambda Event Functions」に改称されました。ここでは2026年11月1日時点の公式ドキュメントで裏の取れる手順だけを使い、テーブル作成から登録・取得・更新・削除の4操作を持つAPIの公開、後片付けまでを通しでたどります。

まとめ

  • 開発環境はAWS CloudShellか、VS CodeにAWS Toolkitを入れたローカル環境です。AWS Cloud9は新規のAWSアカウントでは選べません。
  • ランタイムはpython3.13かpython3.14を選びます。python3.8〜python3.10は廃止済みで、2027年7月29日から新規作成もブロックされます。
  • DynamoDBのテーブルはオンデマンド(PAY_PER_REQUEST)で作ります。ProvisionedThroughputとの同時指定はAPIが拒否します。
  • APIキー・使用量プラン・AWS WAF・テスト呼び出しが要るならREST API、要らないならHTTP APIです。この記事はREST APIで進めます。
  • AWS CLIで統合を作るときはaws lambda add-permissionを忘れると呼び出しに失敗します。コンソールが裏で付けている権限なので、CLI手順だけで抜けます。
  • 終わったらAPI・関数・ロググループ・テーブル・IAMロールの5つを消します。IAMロールはポリシーを外してからでないと削除できません。

開発環境の用意から順に手を動かします。

始める前の準備|開発環境・ランタイム・費用

古い手順記事をなぞる前に、開発環境・ランタイム・費用の3点を今の提供状況に合わせます。開発環境とランタイムは、どちらも2024年以降にAWS側で変わりました。

AWS Cloud9に代わる開発環境

AWS DevOpsブログの移行案内は「we have made the decision to close new customer access to AWS Cloud9, effective July 25, 2024」と明示しています。既存顧客は引き続き使えますが、新しく作るアカウントでは利用できません。

同じブログが挙げる移行先は、ログイン中の認証情報とAWS CLIが使える状態で立ち上がるブラウザのシェル「AWS CloudShell」と、VS Code・IntelliJ・PyCharm向けプラグインの「AWS IDE Toolkits」です。この記事のコマンドはBash系のシェルを前提にしており、どちらでも通ります。ローカルで進める場合はAWS CLI v2とPython 3.10以上を入れ、python3 -m pip install boto3でテーブル作成用のboto3を導入し、aws configureで認証情報を設定します。課金せず手元で試したいならLocalStackとは?AWSをローカルで動かす手順と有料化後の料金・判断基準の構成もありますが、2026年3月23日にCommunityエディションが終了している点に注意してください。

Lambdaランタイムの廃止スケジュール

廃止後のランタイムはコンソールから関数を作成・更新できず、CLI・AWS SAM・CloudFormationからの作成も下表の「新規作成のブロック日」で止まります。

ランタイム識別子 OS 廃止日 新規作成のブロック日
python3.15 Amazon Linux 2023 未定(パブリックプレビュー) 未定
python3.14 Amazon Linux 2023 2029年6月30日 2029年7月31日
python3.13 Amazon Linux 2023 2029年6月30日 2029年7月31日
python3.12 Amazon Linux 2023 2028年10月31日 2028年11月30日
python3.11 Amazon Linux 2 2027年6月30日 2027年7月29日
python3.10 Amazon Linux 2 2026年10月31日 2027年7月29日
python3.9 Amazon Linux 2 2025年12月15日 2027年7月29日
python3.8 Amazon Linux 2 2024年10月14日 2027年7月29日

日付はLambda開発者ガイドのランタイム一覧の値(2026年11月1日確認)で、これより後の日付はすべて予定日です。2026年8月時点ではpython3.8とpython3.9のブロック日が2027年2月1日でしたが、現在は2027年7月29日に延びています。python3.15はプレビュー扱いで、同ページは本番ワークロードに使わないよう注記しているため、新規に作るならpython3.13かpython3.14です。識別子の書き換えは自動では行われません。

ハンズオンにかかる費用と無料プラン

この手順で課金対象になるのは、Lambdaのリクエスト数と実行時間、API Gatewayのリクエスト数とデータ転送、DynamoDBの読み書きとストレージ、CloudWatch Logsの取り込みと保管です。Lambdaには月100万リクエスト・40万GB秒の無料枠があり、API GatewayのREST APIは料金ページの計算例で100万リクエストあたり3.50 USDなので、数十回の動作確認で問題になる額ではありません。2025年7月15日以降の新規アカウントは、登録時に無料プランか有料プランを選び、どちらでも登録時100 USD・条件達成で最大100 USD追加のクレジットを受け取れます。無料プランは6か月経過かクレジットを使い切った時点で終わります。費用が残り続けるのはCloudWatch Logsの保管料のように「消し忘れたもの」なので、最後の後片付けまでを1セットにしてください。東京リージョンの単価で見積もり直す場合はAPI Gatewayの料金を東京リージョンの実額で計算する:REST・HTTP・WebSocketの単価と付帯課金を使えます。

DynamoDBテーブルの作成|オンデマンドで作る理由

最初に、以降のAWS CLIの実行先を東京リージョンに固定します。この記事のPythonコード・IAMポリシー・ARNはすべてap-northeast-1で書いているので、別のリージョンを使う場合はそれらも合わせて書き換えてください。

export AWS_DEFAULT_REGION=ap-northeast-1
export AWS_REGION=ap-northeast-1

続いてユーザー情報を格納するテーブルを1つ作ります。パーティションキーはUserID(文字列)だけの単純な構成です。

import boto3

dynamodb = boto3.resource("dynamodb", region_name="ap-northeast-1")
table = dynamodb.create_table(
    TableName="Users",
    KeySchema=[{"AttributeName": "UserID", "KeyType": "HASH"}],
    AttributeDefinitions=[{"AttributeName": "UserID", "AttributeType": "S"}],
    BillingMode="PAY_PER_REQUEST",
)
table.wait_until_exists()
print(table.table_name, "is ready")

ProvisionedThroughputに読み書き5ユニットずつを指定する手順もよく見かけます。DynamoDBのCreateTable APIリファレンスは、PROVISIONEDならProvisionedThroughputが必須、PAY_PER_REQUESTなら指定不可と定めており、両方書くとValidationExceptionで止まります。同リファレンスは大半のワークロードにPAY_PER_REQUESTを推奨しており、オンデマンドの単価は2024年11月1日に50%引き下げられました。容量の見積もりが要らず、リクエストが来ない時間は読み書きの料金がかからないので、学習用にはオンデマンドが扱いやすい選択です。プロビジョンドにはリージョンごとに25 WCU・25 RCUの無料枠があるので、長期間置いておくテーブルならそちらで組む手もあります。

wait_until_exists()を挟むのは、CreateTableが非同期で、応答時点のステータスがCREATINGだからです。このメソッドは20秒間隔で状態を確認し、25回で例外を投げます。テーブル設計の考え方はAmazon DynamoDBとは:RDSとの違い・料金の仕組み・使い方と設計の要点で扱っています。

Lambda関数の作成|最小権限のIAMロールとCRUDのハンドラ

実行ロールと信頼ポリシー

まずLambdaが引き受けられるロールを作ります。次のJSONをtrust-policy.jsonとして作業ディレクトリに保存してください。信頼ポリシーのServiceがlambda.amazonaws.comでないと、ロールは作れても関数に設定できません。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Service": "lambda.amazonaws.com" },
      "Action": "sts:AssumeRole"
    }
  ]
}

権限はテーブルを限定したインラインポリシーにします。AWS公式チュートリアルのポリシーも"Resource": "*"で、AmazonDynamoDBFullAccessをアタッチする手順も広く出回っていますが、後者はアカウント内の全テーブルにdynamodb:*を許可し、lambda:DeleteFunctionまで含みます。ハンズオンのロールは後で本番のテンプレートに流用されがちなので、最初から4操作に絞ってください。次のJSONをusers-table-policy.jsonとして保存します。以降のコマンドに出てくる123456789012は、すべて自分のAWSアカウントID(aws sts get-caller-identity --query Account --output textで確認できます)に置き換えます。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:PutItem",
        "dynamodb:GetItem",
        "dynamodb:UpdateItem",
        "dynamodb:DeleteItem"
      ],
      "Resource": "arn:aws:dynamodb:ap-northeast-1:123456789012:table/Users"
    }
  ]
}
aws iam create-role --role-name UserApiLambdaRole \
  --assume-role-policy-document file://trust-policy.json

aws iam put-role-policy --role-name UserApiLambdaRole \
  --policy-name UsersTableAccess \
  --policy-document file://users-table-policy.json

aws iam attach-role-policy --role-name UserApiLambdaRole \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

3つ目のAWSLambdaBasicExecutionRoleはCloudWatch Logsへの書き込み権限です。これが無いとログが1行も残らず、後述する502の原因を調べられません。

関数コードとレスポンス形式

API GatewayのLambdaプロキシ統合では、戻り値の形が決まっています。statusCodeと文字列のbodyを軸にしたJSONで、追加のヘッダーが要らなければheadersは省略できます。開発者ガイドは「If the function output is of a different format, API Gateway returns a 502 Bad Gateway error response.」と明記しています。DynamoDBから取った項目をそのままreturnすると502になるのはこの規定のためです。

次のコードはPOST /usersで登録、GET・PUT・DELETE /users/{UserID}で取得・更新・削除を行います。

import json
import os

import boto3
from botocore.exceptions import ClientError

TABLE = boto3.resource("dynamodb").Table(os.environ["TABLE_NAME"])


def _response(status, payload):
    return {
        "statusCode": status,
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps(payload, ensure_ascii=False, default=str),
    }


def _not_found(err):
    return err.response["Error"]["Code"] == "ConditionalCheckFailedException"


def lambda_handler(event, context):
    method = event.get("httpMethod")
    user_id = (event.get("pathParameters") or {}).get("UserID")
    try:
        body = json.loads(event["body"]) if event.get("body") else {}
    except json.JSONDecodeError:
        return _response(400, {"message": "body must be JSON"})
    if not isinstance(body, dict):
        return _response(400, {"message": "body must be a JSON object"})
    name = body.get("Name", "")
    if not isinstance(name, str):
        return _response(400, {"message": "Name must be a string"})

    if user_id is None:
        if method != "POST":
            return _response(405, {"message": "Method not allowed"})
        new_id = body.get("UserID")
        if not isinstance(new_id, str) or not new_id:
            return _response(400, {"message": "UserID (string) is required"})
        try:
            TABLE.put_item(
                Item={"UserID": new_id, "Name": name},
                ConditionExpression="attribute_not_exists(UserID)",
            )
        except ClientError as err:
            if _not_found(err):
                return _response(409, {"message": "User already exists"})
            raise
        return _response(201, {"UserID": new_id})

    if method == "GET":
        item = TABLE.get_item(Key={"UserID": user_id}).get("Item")
        if item is None:
            return _response(404, {"message": "User not found"})
        return _response(200, item)

    if method == "PUT":
        try:
            item = TABLE.update_item(
                Key={"UserID": user_id},
                UpdateExpression="SET #n = :n",
                ExpressionAttributeNames={"#n": "Name"},
                ExpressionAttributeValues={":n": name},
                ConditionExpression="attribute_exists(UserID)",
                ReturnValues="ALL_NEW",
            )["Attributes"]
        except ClientError as err:
            if _not_found(err):
                return _response(404, {"message": "User not found"})
            raise
        return _response(200, item)

    if method == "DELETE":
        try:
            TABLE.delete_item(
                Key={"UserID": user_id},
                ConditionExpression="attribute_exists(UserID)",
            )
        except ClientError as err:
            if _not_found(err):
                return _response(404, {"message": "User not found"})
            raise
        return _response(200, {"deleted": user_id})

    return _response(405, {"message": "Method not allowed"})

このコードはDynamoDBのモック(moto 5.1.22)に対して全分岐を実行し、不正な入力を含めて想定どおり201・409・400・200・404・405を返すことを確かめています。行数のわりに条件が多いのは、どれも502か意図しない上書きを防ぐためです。

  • ExpressionAttributeNamesで#nに置き換えているのは、NameがDynamoDBの予約語だからです。SET Name = :nと直接書くと「Attribute name is a reserved keyword」のValidationExceptionで落ち、未処理の例外として502になります。
  • put_itemとupdate_itemは既定で上書き・新規作成をします。attribute_not_existsとattribute_existsの条件を付けないと、POSTで既存ユーザーが消え、存在しないIDへのPUTで項目が増えます。
  • 本文がJSONのオブジェクトか、UserIDとNameが文字列かを先に確かめています。配列やnullが届くとbody.getがAttributeErrorになり、Nameに小数を渡すとboto3がfloatを受け付けずTypeErrorを出します。キーをS型で定義したテーブルに数値のUserIDを渡すとValidationExceptionで、DynamoDBはキー属性に空文字も許しません。
  • default=strは、DynamoDBが数値をDecimalで返しjson.dumpsがそのままでは変換できない問題への対処です。この指定では3が"3"という文字列で返るため、数値のまま返したい場合はint()かfloat()へ変換するエンコーダーを書きます。

テーブル名を環境変数から読むのは、ステージごとにテーブルを分けたときに関数コードを書き換えずに済ませるためです。このファイルをlambda_function.pyとして保存します。

デプロイと関数単体のテスト

boto3はLambdaのPythonランタイムに同梱されているので、この関数に外部依存はありません。ファイル1つをzipで固めてデプロイします。

zip function.zip lambda_function.py

aws lambda create-function \
  --function-name UserFunction \
  --runtime python3.13 \
  --role arn:aws:iam::123456789012:role/UserApiLambdaRole \
  --handler lambda_function.lambda_handler \
  --environment "Variables={TABLE_NAME=Users}" \
  --zip-file fileb://function.zip

aws lambda wait function-active-v2 --function-name UserFunction

echo '{"httpMethod":"POST","pathParameters":null,"body":"{\"UserID\":\"u-000\"}"}' > event.json
aws lambda invoke --function-name UserFunction \
  --cli-binary-format raw-in-base64-out \
  --payload file://event.json out.json
cat out.json

API Gatewayをつなぐ前に関数だけを呼んでおくと、502が出たときに原因が関数側かAPI側かを切り分けられます。作成直後の関数はPending状態で呼び出しに失敗することがあるので、wait function-active-v2でActiveになるのを待ってから呼びます。AWS CLI v2はペイロードを既定でbase64として解釈するため、JSONを直接渡すには--cli-binary-format raw-in-base64-outが要ります。out.jsonに"statusCode": 201が入っていれば関数は正常です。本番では開発者ガイドの推奨どおり、同梱SDKに頼らずデプロイパッケージかLayerにboto3を含めます。料金やコールドスタートの考え方はAWS Lambdaとは?仕組み・料金体系とコールドスタート対策・採用判断を実装者目線で解説にまとめています。

API Gatewayでの公開|REST APIとHTTP APIの判断基準

REST APIとHTTP APIの機能差

機能 REST API HTTP API
APIキー・使用量プラン あり なし
AWS WAF連携 あり なし
プライベートエンドポイント あり なし
リクエスト検証・キャッシュ あり なし
コンソールのテスト呼び出し あり なし
レスポンスストリーミング あり なし
実行ログ・X-Rayトレース あり なし
JWTオーソライザー なし あり
自動デプロイ なし あり
100万リクエストの単価(料金ページの計算例) 3.50 USD 1.00 USD

機能の対応はAPI Gateway開発者ガイドの比較ページによります。REST APIのレスポンスストリーミングは2025年11月19日に追加された機能で、統合タイムアウトを最大15分にでき、10 MBを超えるレスポンスも返せます。判断の軸は単純で、APIキーを配って利用量を制御する、AWS WAFを前段に置く、のどちらかが要件にあればREST API、社内APIやモバイルアプリのバックエンドのように鍵の配布が不要ならHTTP APIです。この記事はテスト呼び出しを使いたいのでREST APIで進めます。認可方式の選び方はAPI Gatewayの認証:Cognito・JWT・Lambdaオーソライザー・IAM認可の使い分けと設定例で比較しています。

HTTP APIに切り替える場合は関数コードも直します。ペイロード形式2.0ではHTTPメソッドがevent["httpMethod"]ではなくevent["requestContext"]["http"]["method"]に入ります。AWS CLI・CloudFormation・SDKから統合を作るときはpayloadFormatVersionの明示が必須です。

REST APIの公開手順

REGION=ap-northeast-1
ACCOUNT=123456789012
FN_ARN=arn:aws:lambda:$REGION:$ACCOUNT:function:UserFunction

API_ID=$(aws apigateway create-rest-api --name UserAPI --query id --output text)
ROOT_ID=$(aws apigateway get-resources --rest-api-id $API_ID \
  --query "items[?path=='/'].id" --output text)

USERS_ID=$(aws apigateway create-resource --rest-api-id $API_ID \
  --parent-id $ROOT_ID --path-part users --query id --output text)
ITEM_ID=$(aws apigateway create-resource --rest-api-id $API_ID \
  --parent-id $USERS_ID --path-part "{UserID}" --query id --output text)

for RID in $USERS_ID $ITEM_ID; do
  aws apigateway put-method --rest-api-id $API_ID --resource-id $RID \
    --http-method ANY --authorization-type NONE
  aws apigateway put-integration --rest-api-id $API_ID --resource-id $RID \
    --http-method ANY --type AWS_PROXY --integration-http-method POST \
    --uri arn:aws:apigateway:$REGION:lambda:path/2015-03-31/functions/$FN_ARN/invocations
done

aws lambda add-permission --function-name UserFunction \
  --statement-id apigw-users --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-arn "arn:aws:execute-api:$REGION:$ACCOUNT:$API_ID/*/*/users"
aws lambda add-permission --function-name UserFunction \
  --statement-id apigw-users-item --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-arn "arn:aws:execute-api:$REGION:$ACCOUNT:$API_ID/*/*/users/*"

aws apigateway create-deployment --rest-api-id $API_ID --stage-name prod

add-permissionは、API GatewayにこのLambda関数の呼び出しを許可するリソースベースポリシーを付けるコマンドです。開発者ガイドはput-integrationの--credentialsにIAMロールを渡す方法を示したうえで、「Instead of supplying an IAM role for credentials, you can use the add-permission command to add resource-based permissions. This is what the API Gateway console does.」と補足しています。コンソールで統合を選ぶと裏でこのリソースベースポリシーが付くため、コンソール前提の手順をCLIに書き写すと権限だけが抜けます。この例では/usersと/users/{UserID}のパスごとに許可を分けて2本にしています。ワイルドカードで1本にまとめることもできますが、許可の範囲が広がる点は承知のうえで選んでください。ACCOUNTとREGIONは自分のアカウントIDと利用リージョンに置き換えてください。

統合のHTTPメソッドをPOSTにしているのはLambdaの仕様です。クライアントがGETやDELETEで呼んでも、API GatewayからLambdaへの呼び出しはPOSTでなければならず、--integration-http-methodにクライアント側のメソッドを書くと動きません。

動作確認とつまずきどころ|502 Bad GatewayとCORS

デプロイが済んだら、エンドポイントhttps://<api-id>.execute-api.<region>.amazonaws.com/prod/usersを叩きます。

curlとテスト呼び出しによるCRUDの動作確認

BASE="https://$API_ID.execute-api.$REGION.amazonaws.com/prod/users"

curl -i -X POST "$BASE" -H "Content-Type: application/json" \
  -d '{"UserID": "u-001", "Name": "Taro"}'
curl -i "$BASE/u-001"
curl -i -X PUT "$BASE/u-001" -H "Content-Type: application/json" \
  -d '{"Name": "Jiro"}'
curl -i -X DELETE "$BASE/u-001"

aws apigateway test-invoke-method --rest-api-id $API_ID \
  --resource-id $ITEM_ID --http-method GET \
  --path-with-query-string "/users/u-001"

-iを付けているので、応答の1行目にHTTPステータスが出ます。上から201、200、200、200の順で返れば完成です。最後のtest-invoke-methodはデプロイせずにメソッドを実行し、API Gatewayが関数に渡したイベントと受け取った戻り値をログ付きで返します。比較表のとおりREST API専用の機能で、curlでは見えない「統合のどこで落ちたか」がわかります。

POSTの直後のGETで404が返ることがあります。get_itemは既定で結果整合性のある読み込みで、DynamoDBのAPIリファレンスも「GetItem provides an eventually consistent read by default.」としています。確実に読みたい場合はConsistentRead=Trueを渡すか、数秒おいて再実行してください。

502 Bad Gatewayが返るときの切り分け

Lambda開発者ガイドは「If the Event Function runs but returns an error, or returns a response in the wrong format, API Gateway returns a 502.」としており、関数がエラーで終わった場合と、戻り値の形式が誤っている場合の両方が502になります。よく踏むのは、statusCodeを含まない値を返す、bodyをdictのまま返す、未処理の例外で終わる、同期呼び出しの上限6 MBを超えるレスポンスを返す、の4つです。調べる場所は原因によって違います。未処理の例外はCloudWatch Logsの/aws/lambda/UserFunctionにトレースが出ますが、関数が正常終了して形式の誤った値を返した場合はLambda側のログにエラーが残らないため、前述のtest-invoke-methodかAPI Gatewayの実行ログで戻り値を確認します。前掲のコードは形式の誤りを_responseで、例外の主因(予約語・型違い・Decimal)を個別に潰しています。実行ログの有効化はAPI Gatewayのログ設定:実行ログとアクセスログをCloudWatchに出す手順とX-Rayでの障害調査で手順を解説しています。

ブラウザから呼ぶときのCORS設定

curlは通るのにフロントエンドからの呼び出しが失敗する場合は、まずブラウザの開発者ツールのコンソールにCORSのエラーが出ているかと、OPTIONSリクエストの応答を確認します。CORSが原因なら、プロキシ統合ではAPI Gateway側の設定だけでは足りず、開発者ガイドは「To enable CORS for the Lambda proxy integration, you must add Access-Control-Allow-Origin:{domain-name} to the output headers.」としています。前掲の_responseなら、ヘッダーを次のように変えます。

        "headers": {
            "Content-Type": "application/json",
            "Access-Control-Allow-Origin": "https://app.example.com",
        },

開発中は*でも動きますが、本番では自社のオリジンを指定します。PUT・DELETEと、Content-Type: application/jsonを付けたPOSTはプリフライトのOPTIONSリクエストを伴います。API Gateway側でOPTIONSメソッドを作ってCORSを有効にするか、関数の先頭でOPTIONSを受け、Access-Control-Allow-Origin・Access-Control-Allow-Methods・Access-Control-Allow-Headers(値にContent-Typeを含める)の3つを付けた応答を返します。どちらの場合も、設定を変えたらcreate-deploymentで再デプロイしてください。

後片付け|課金を残さない削除の順番

ハンズオンのリソースはほとんど従量課金なので放置しても大きな額にはなりませんが、ロググループは関数を消しても残り、保存期間が無期限のままだと保管料がかかり続けます。認可なしのエンドポイントも、URLを知っていれば誰でも書き込める状態で残ります。

aws apigateway delete-rest-api --rest-api-id $API_ID
aws lambda delete-function --function-name UserFunction
aws logs delete-log-group --log-group-name /aws/lambda/UserFunction
aws dynamodb delete-table --table-name Users

aws iam detach-role-policy --role-name UserApiLambdaRole \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
aws iam delete-role-policy --role-name UserApiLambdaRole \
  --policy-name UsersTableAccess
aws iam delete-role --role-name UserApiLambdaRole

IAMロールは、アタッチしたマネージドポリシーとインラインポリシーが残っているとDeleteConflictで削除できません。外す→消す→ロール削除の順番を守ってください。他のハンズオンで作ったリソースの洗い出し方や、公式教材の探し方はAWSハンズオンの探し方と後片付け:公式教材の入口と課金を残さないCLI手順にまとめています。

API Gatewayを使わない選択肢|Function URLsとLambda Web Functions

LambdaをHTTPで呼ぶ方法はAPI Gatewayだけではありません。2026年10月の発表で選択肢が1つ増えたので、ハンズオンの構成を実際のサービスに持ち込む前に並べておきます。

方式 コードの形 認可 エンドポイントの追加料金 言語
API Gateway+Event Function イベントハンドラ IAM・Cognito・Lambdaオーソライザーほか あり 全ランタイム
Function URLs+Event Function イベントハンドラ なし/IAM なし 全ランタイム
Lambda Web Functions ポートで待つHTTPサーバー 発表に記載なし なし(関数をCPU時間とメモリで課金) Node.js 24のみ

Lambda開発者ガイドの選び方のページは、単純なアプリや試作にはFunction URLs、大規模な本番やOpenAPI・認証の選択肢・カスタムドメイン・スロットリングが要る場合はAPI Gatewayを推奨しています。Function URLsはURL自体に追加料金がかからない代わりに、認可は「なし」か「IAM」しか選べず、リクエスト検証もありません。社内の管理画面や一般ユーザー向けのAPIをFunction URLsの認可なしで公開するのは避けてください。

2026年10月12日に発表されたLambda Web Functionsは、コードがポートでHTTPリクエストを直接受ける新しい実行形態です。ExpressやHonoのアプリをアダプタなしで載せられ、1つの実行環境で最大128リクエストを同時に処理し、待ち時間ではなく実際に使ったCPU時間で課金されます。提供開始時点のランタイムはArm64上のnodejs24.xだけで、提供リージョンは東京を含む5つです。同時に既存のLambda関数は「Lambda Event Functions」に改称されましたが、名前が変わっただけでコードの変更や再デプロイは不要です。この記事のようにPythonでDynamoDBを読み書きするAPIは、引き続きEvent FunctionsとAPI Gatewayの組み合わせで作ります。

手作業のCLIから抜けるタイミング|AWS SAMへの書き直し

CLI手順はリソース同士の関係を理解するには有効ですが、差分適用もロールバックもないため、ステージや関数を増やすたびに手順書が伸びます。復習を兼ねて同じ構成をAWS SAMのテンプレートで書き直すと、テーブル・関数・APIが1枚に収まり、sam deployで差分だけが適用されます。SAM CLIの使い方はAWS SAMとは?CloudFormationとの違い・SAM CLIの使い方を実例で解説、複数クラウドを視野に入れる場合の選択肢はServerless Frameworkとは?v4の料金・使い方・Python対応と無料の代替oslsまで【2026年10月】で確認できます。サーバーレス構成そのものの向き不向きはサーバーレスアーキテクチャとは?構成パターン・AWS実装・採用判断を実装者目線で解説で整理しています。ハンズオンの構成を業務システムのAPI基盤として設計し直す段階では、AWSのインフラ構築支援で権限設計やIaC化から相談を受けています。

よくある質問

AWS Cloud9がコンソールに見当たりません。何を使えばよいですか?

AWS Cloud9は2024年7月25日に新規顧客への提供を終了し、新しいアカウントでは選べません。AWSの移行案内はAWS CloudShellとAWS IDE Toolkitsの2つで、ブラウザで完結させたいならCloudShellです。

python3.10で作ったLambda関数はいつまで動きますか?

呼び出し自体は廃止後も続けられます。python3.10は2026年10月31日に廃止日を迎え、コンソールからの作成・更新ができなくなりました。CLIやAWS SAMからの新規作成は2027年7月29日から、既存関数の更新は2027年8月31日からブロックされます。廃止後のランタイムにはセキュリティパッチが当たらないため、python3.13かpython3.14へ識別子を書き換えてテストしてください。

コンソールの表示が「Lambda関数」から「Event Functions」に変わりました。何か対応が要りますか?

対応は不要です。2026年10月12日のLambda Web Functionsの発表に合わせて、既存のLambda関数がLambda Event Functionsに改称されました。名前の変更のみで、コードの修正や再デプロイは要りません。

REST APIとHTTP APIのどちらで作るべきですか?

APIキーと使用量プラン、AWS WAF、リクエスト検証、キャッシュ、テスト呼び出し、レスポンスストリーミングのいずれかが要るならREST APIです。どれも要らないならHTTP APIのほうが安く、料金ページの計算例では100万リクエストあたり1.00 USDと3.50 USDの差があります。HTTP APIではペイロード形式2.0が既定になり、関数側でイベントの読み取り位置を変える必要があります。

認可タイプをNONEのままにしても問題ありませんか?

ハンズオンでは動かすことを優先して--authorization-type NONEにしていますが、この状態のエンドポイントはURLを知っていれば誰でもPUTやDELETEを呼べます。動作確認が済んだら削除するか、認可を足してください。REST APIならIAM認可・Amazon Cognitoオーソライザー・Lambdaオーソライザーが選べ、社内利用ならIAM認可、一般ユーザー向けならCognitoが出発点です。

関連記事

お気に入りに入れた記事の一覧

一創の公式 SNS

X では毎朝の新着記事と IT ニュースの速報を、YouTube・TikTok・Instagram・LinkedIn・Facebook では記事の解説動画を配信しています。

この記事は以下の記事からリンクされています

ほか 2 件の記事からもリンクされています。

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.29 テックブログ ソニー生命の情報漏洩38.5万件|ログインできない理由、再開状況と窓口の電話番号
  2. 2026.10.31 テックブログ 松屋の不正アクセス:237万件の対象確認と窓口、漏れたパスワードの守り方
  3. 2026.11.01 コラム インボイスの経過措置とは?70%控除の期間と計算方法・帳簿の書き方・1億円制限【2026年10月改正対応】
  4. 2026.03.17 コラム 所得金額調整控除とは?年収850万円超で最大15万円、対象者と計算式・申告書の書き方【令和8年分】
  5. 2026.11.02 テックブログ サントリーの不正アクセスで約10万人分漏えい|M365のファイル大量ダウンロードを監査ログで調べる手順

RELATED POSTS 関連記事

目次