はじめに
今回は、API Gateway(HTTP API)、Lambda、DynamoDBによる基本的なサーバレスAPIアーキテクチャをCDKで構築したのでご紹介させていただきます。
AWSの公式デベロッパーガイドにあるLambda と DynamoDB を使用して CRUD HTTP API を作成するというチュートリアルを参考に、CDKを実装しました。
ソースコードはこちらのGitHubで公開しております。
事前準備
- AWSアカウントが作成されていること
- CDK開発環境の構築が完了していること
(詳しくはこちらをご参照ください。)
AWS 構成図

HTTP APIを呼び出すと、API GatewayがリクエストをLambdaにルーティングし、DynamoDBでデータの読み書きを行う構成となっています。
コードの紹介
コードの全容はこちらのGitHubからご確認ください。
import * as cdk from 'aws-cdk-lib/core';
import { Construct } from 'constructs';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigwv2 from 'aws-cdk-lib/aws-apigatewayv2';
import * as integrations from 'aws-cdk-lib/aws-apigatewayv2-integrations';
import * as path from 'path';
interface ServerlessApiStackProps extends cdk.StackProps {
// パラメータファイル読み込み時に紐づける環境名
nodeEnv: string;
}
export class ServerlessApiStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: ServerlessApiStackProps) {
super(scope, id, props);
/**
* 事前定義
*/
// パラメータファイル読み込み(用途に応じて変更する可能性のあるパラメータは外部ファイルに記述)
const config = require("../config/" + props.nodeEnv);
/**
* DynamoDB
*/
const itemsTable = new dynamodb.Table(this, "ItemsTable", {
tableName: `${config.common.project}-${config.common.env}-items-table`,
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
/**
* Lambda
*/
const apiFunction = new lambda.Function(this, "ApiFunction", {
functionName: `${config.common.project}-${config.common.env}-api-function`,
runtime: lambda.Runtime.PYTHON_3_14,
handler: "lambda_function.lambda_handler",
code: lambda.Code.fromAsset(path.join(__dirname, "../lambda")),
environment: {
TABLE_NAME: itemsTable.tableName,
},
});
// Lambda→DynamoDBへのReadWrite権限付与
itemsTable.grantReadWriteData(apiFunction);
/**
* API GateWay
*/
const httpApi = new apigwv2.HttpApi(this, "HttpApiApi", {
apiName: `${config.common.project}-${config.common.env}-http-api`,
});
// Lambda統合を作成
const integration = new integrations.HttpLambdaIntegration(
"LambdaIntegration",
apiFunction
);
const routes = [
{ method: apigwv2.HttpMethod.GET, path: '/items/{id}' },
{ method: apigwv2.HttpMethod.GET, path: '/items' },
{ method: apigwv2.HttpMethod.PUT, path: '/items' },
{ method: apigwv2.HttpMethod.DELETE, path: '/items/{id}' },
];
routes.forEach(route => {
httpApi.addRoutes({
path: route.path,
methods: [route.method],
integration
});
});
}
}
ポイント①
DynamoDBはデータを保持している場合、Stackを削除しても残ります。今回はあくまで検証で作成をしたかったのでremovalPolicyでDESTROYを指定することでStack削除とともにDynamoDBも削除されるように設定しました。
ポイント②
Lambdaの環境変数でDynamoDBテーブルのテーブル名を引き渡しています。これによりLambdaに対して動的にテーブル名を引き渡せるため、デプロイ後のテーブル名修正などがしやすい構成となります。
それに伴って、Lambdaのコードも一部チュートリアルのものに修正を加えています。
ポイント③
LambdaからDynamoDBへのアクセス権付与において、grantメソッドを使うことで簡単に必要最小限なアクセス許可を設定しています。
ポイント④
API Gatewayにルートを追加する部分をfor文で実装しました。同じような処理をする際に繰り返し処理を利用できるのもCDKのメリットかなと思いました。
CRUD動作確認
まずは環境変数に作成したAPI URLを指定します。API Gatewayのコンソール画面にいき、呼び出しURLを確認します。

$ export API_URL=https://6oq6hmoxdc.execute-api.ap-northeast-1.amazonaws.com
作成
$ curl -X "PUT" -H "Content-Type: application/json" -d "{\"id\": \"123\", \"price\": 12345, \"name\": \"myitem\"}" $API_URL/items
"Put item 123"
HTTPメソッドでPUTを指定します。上記コマンドにより、項目を新規作成します。
後で確認する更新とコマンドは同じで、IDが既存のものであれば更新処理となります。
取得
$ curl $API_URL/items
[[{"price": 12345.0, "id": "123", "name": "myitem"}]]
作成した項目が存在することを確認できました。
IDで1つの項目を取得する場合は、末尾に表示したいIDを指定します。
$ curl $API_URL/items/123
[{"price": 12345.0, "id": "123", "name": "myitem"}]
更新
$ curl -X "PUT" -H "Content-Type: application/json" -d "{\"id\": \"123\", \"price\": 54321, \"name\": \"myitem2\"}" $API_URL/items
"Put item 123"
作成した項目と同じIDで値段と名前を変更してコマンドを実行してみます。
$ curl $API_URL/items/123
[{"price": 54321.0, "id": "123", "name": "myitem2"}]
すると、変更後の値に更新されていることを確認できました。
削除
$ curl -X "DELETE" $API_URL/items/123
"Deleted item 123"
最後に削除です。HTTPメソッドでDELETEを指定することで項目を削除できます。
$ curl $API_URL/items
[]
まとめ
本ブログでは、API Gateway(HTTP API)、Lambda、DynamoDBによる基本的なサーバレスAPIのアーキテクチャをCDKによって実装したことについて紹介しました。
チュートリアルに沿って実装しつつ、一部CDKによる実装のメリットをうまく取り入れられたかなと思います。ただチュートリアルを実施する以上に、理解を深めることができました。