Lambda LayersでPrisma Clientを共有してLambdaからRDSに接続してみた

Lambda LayersでPrisma Clientを共有してLambdaからRDSに接続してみた

2025.11.16

こんにちは。クラウド事業本部の桑野です。

以前、TerraformでRDSを作成し、Prisma CLIを使ってマイグレーションやシードをやってみました。
今回は前回の予告通り、Prisma Clientを使ったORMをAWS Lambdaで実装してみます。
Prisma Clientはパッケージサイズが大きいため、Lambda Layersに配置することで各関数を軽量に保つことができます。

以前の記事

https://dev.classmethod.jp/articles/prisma-cli-terraform-rds/

Lambda Layersとは

Lambda レイヤーは、補助的なコードやデータを含む .zip ファイルアーカイブです。

つまりLambda関数起動に必要なパッケージやデータを外付けにして管理できちゃう仕組みですね。

今回使用するPrisma Clientは大きなサイズのパッケージです。
これを一つ一つのLambda関数に含めてしまうと、デプロイするたびに大きなサイズのファイルをLambdaへアップロードすることになり、変更までの時間が長くなります。
Lambda Layersに配置し、各Lambda関数から参照させることで、関数自体は軽量な状態に保てます。
関数のコードを変更した際も、高速にデプロイできるというメリットがあります。

Node.jsランタイムの場合、下記の通り構成します。

layer.zip
└── nodejs/
    └── node_modules/
        ├── .prisma/
        ├── @backend/
        ├── @prisma/
        └── (その他の依存パッケージ)

Lambda は実行時にレイヤーの内容を /opt ディレクトリに展開し、Node.jsランタイムが自動的にパッケージを認識します。
関数コードからは通常の require()import でパッケージを参照できます。

構成内容

下記の構成をTerraformで構築し、Lambda関数でRDSを操作します。
Terraform、およびPrisma CLIはそれぞれDockerコンテナから実行します。

  • Prisma ClientはPackage化し、Lambda Layersとしてアップロードする
  • データソースはAmazon RDS for MariaDBを使用
  • Node.jsをランタイムとするLambda関数からRDSに対してクエリを実行する

前提

下記の条件で検証しています。

  • OS:macOS Sequoia バージョン 15.6.1
  • チップ:Apple M4
  • Docker Client:28.4.0
  • Docker Server:28.3.3
  • Colima:0.8.4
  • docker compose:2.39.3

構築手順については以下の記事をご参照ください。

https://dev.classmethod.jp/articles/colima-docker-terraform/

また、Terraform実行用にAWS CLIのプロファイルが必要になります。
IAMユーザーのクレデンシャルを指定するので、あらかじめアクセスキーとシークレットアクセスキーを発行しておきましょう。

構築

コードは下記GitHubリポジトリにて公開しています。

https://github.com/k-kuwan0/prisma-on-lambda-layers

1. Backendプロジェクトのパッケージインストール

backendというコンテナにアクセスします。
以下のコマンドを実行し、ワークスペース全体のパッケージをインストールします。

cd /backend/typescript
pnpm install

2. Prismaのセットアップ

続いて、Prismaの型とエンジンを生成します。

pnpm prisma:generate

3. ソースコードのビルド

Prismaのセットアップが完了したら、Lambda Layers、およびLambda関数のソースコードをビルドします。

Lambda Layers

以下のコマンドを実行します。

pnpm build:layer

typescriptディレクトリ配下にlayerというディレクトリが作成されます。
このコマンドでは下記の処理を行なっています。

  1. /backend/typescript/layer/nodejs/node_modulesのクリーンアップ
  2. /backend/typescript/packages配下にあるパッケージのビルド
  3. Prisma Clientのコピー

Lambda関数

以下のコマンドを実行します。

pnpm build:functions

typescript/lambda-functionsディレクトリ配下にdistというディレクトリが作成されます。
このコマンドではesbuildを使って各関数をビルドしています。
重要なのは、externalというオプションで、指定したパッケージはバンドルされません。
import文が残るため、Lambda LayersのNode.jsパスを参照させることができます。

以上で、TerraformでAWSリソースをデプロイする準備が整いました。

4. AWS CLIのセットアップ

続いてterraform_runnerというコンテナにアクセスします。
以下のコマンドを実行し、事前に用意していたクレデンシャルを設定します。

aws configure --profile rds-prisma

5. Terraform実行

クレデンシャルの設定が完了したら、Terraformを使用してリソースを作成します。

cd /terraform-runner/terraform/environments/dev
terraform init
terraform apply

6. マイグレーションの実行

Terraformでデプロイが完了したら、再度backendコンテナにアクセスします。
以下のコマンドを実行し、SSM接続を行います。

まずは、環境変数を設定します。
以下のコマンドを実行し、env.exampleをコピーしたenv.devを作成しましょう。

cp /backend/typescript/environments/.env.example /backend/typescript/environments/.env.dev

env.devを作成したら、DB_SECRET_ARNに、Secrets Managerから確認可能なシークレットのARNを追記します。

AWS_REGION="ap-northeast-1"
AWS_PROFILE="rds-prisma"
# ご自身のAWS環境に作成したSecrets ManagerのARNに置き換えてください
DB_SECRET_ARN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
DB_HOST="localhost"
DB_NAME="main"
DB_PORT="3306"

.env.devの編集が完了したらssm start-sessionコマンドを実行します。
instanceIdにはEC2インスタンスのIDを、RDSEndpointにはRDSのエンドポイントをそれぞれ置き換えて実行しましょう。

aws ssm start-session --profile rds-prisma --target instanceId --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters '{"host":["RDSEndpoint"],"portNumber":["3306"],"localPortNumber":["3306"]}'

SSM接続できたら、RDSに対してマイグレーションを行います。
以下のコマンドを実行しましょう。

pnpm prisma:migrate:deploy:dev

All migrations have been successfully applied.と出力されればOKです。

7. Lambda関数のテスト実行

マイグレーションが完了したら、Lambda関数をチェックしてみましょう。
AWSマネジメントコンソールを開き、Lambdaを開きます。
左サイドメニューから関数を選択します。

すると、先ほどデプロイしたLambda関数が3つ表示されます。

  • prisma-rds-manager-dev-lambda-other-todo-get
  • prisma-rds-manager-dev-lambda-other-todo-post
  • prisma-rds-manager-dev-lambda-other-todo-delete

prisma-rds-manager-dev-lambda-other-todo-get

todoテーブルからレコードを取得します。
優先度、完了状態が一致するものを全件取得します。

prisma-rds-manager-dev-lambda-other-todo-post

todoテーブルに新しいレコードを1件作成します。
タイトルが必須で、それ以外については空であってもデフォルト値で作成が可能です。

prisma-rds-manager-dev-lambda-other-todo-delete

todoテーブルから指定したIDに一致するレコードを1件削除します。


順にテスト実行してみましょう。

まずはget関数です。
JSONを空にして実行します。すると以下の通り実行結果が返却されます。

{
  "statusCode": 200,
  "body": "{\"message\":\"Todos retrieved successfully\",\"count\":0,\"todos\":[]}"
}

それもそのはず、まだtodoテーブルには一つもレコードが登録されていないからです。

続いてpost関数を実行してみます。
JSONを下記の通り設定し、実行します。

{
  "title": "AWS Certified Solutions Architect - Associate",
  "description": "試験を予約し、合格する。",
  "priority": "HIGH",
  "dueDate": "2025-12-31T23:59:59Z"
}

実行結果は以下の通りです。

{
  "statusCode": 200,
  "body": "{\"message\":\"Todo created successfully\",\"todo\":{\"id\":\"cmi1fk9cx0000l702yj44wyg8\",\"title\":\"AWS Certified Solutions Architect - Associate\",\"description\":\"試験を予約し、合格する。\",\"completed\":false,\"priority\":\"HIGH\",\"dueDate\":\"2025-12-31T23:59:59.000Z\",\"createdAt\":\"2025-11-16T08:04:57.824Z\",\"updatedAt\":\"2025-11-16T08:04:57.824Z\"}}"
}

再度get関数を実行してみましょう。

{
  "statusCode": 200,
  "body": "{\"message\":\"Todos retrieved successfully\",\"count\":1,\"todos\":[{\"id\":\"cmi1fk9cx0000l702yj44wyg8\",\"title\":\"AWS Certified Solutions Architect - Associate\",\"description\":\"試験を予約し、合格する。\",\"completed\":false,\"priority\":\"HIGH\",\"dueDate\":\"2025-12-31T23:59:59.000Z\",\"createdAt\":\"2025-11-16T08:04:57.824Z\",\"updatedAt\":\"2025-11-16T08:04:57.824Z\"}]}"
}

実行結果として、先ほどpost関数でJSONに設定した情報が配列の要素として返却されます。
つまり、ちゃんとpost関数でレコードが登録されたということがわかります。
次のdelete関数でidを使いますので、控えておきましょう。

最後にdelete関数を実行します。
JSONを下記の通り設定し、実行します。idには先ほど控えた値を入力してください。

{
    "id": "cmi1fk9cx0000l702yj44wyg8"
}

実行結果は以下の通りです。

{
  "statusCode": 200,
  "body": "{\"message\":\"Todo deleted successfully\",\"todo\":{\"id\":\"cmi1fk9cx0000l702yj44wyg8\",\"title\":\"AWS Certified Solutions Architect - Associate\",\"description\":\"試験を予約し、合格する。\",\"completed\":false,\"priority\":\"HIGH\",\"dueDate\":\"2025-12-31T23:59:59.000Z\",\"createdAt\":\"2025-11-16T08:04:57.824Z\",\"updatedAt\":\"2025-11-16T08:04:57.824Z\"}}"
}

レコードが削除されたかどうか、get関数を再び実行して確認してみましょう。
実行結果として空の配列が返却されます。

{
  "statusCode": 200,
  "body": "{\"message\":\"Todos retrieved successfully\",\"count\":0,\"todos\":[]}"
}

きちんとレコードが削除されたということがわかります。

8. AWSリソースの削除

今回作成したリソースは、放っておくとお金がかかり続けます。
実施が終わったあと、リソースを残しておく必要がなければ削除することを忘れずに行なっておきましょう。

terraform_runnerコンテナにアクセスし、下記コマンドを実行します。

terraform destroy

まとめ

いかがだったでしょうか。
Prisma ClientをLambdaで実行したい場合、Lambda Layersに載せることで、各Lambda関数を軽量にすることができます。
しかし、そのためには不要なエンジンを省いたり、各関数でexternalを用いてバンドルに含めないという工夫も必要だったりと意外に大変でした。
Lambda関数は頻繁に変更を加えることが多いため、デプロイ時間の短縮は長期的なメリットをもたらします。
ぜひ、Node.jsランタイムのLambdaでDB操作をする際にはPrisma ClientとLambda Layersを試してみてください!!

最後までご覧いただきありがとうございました。

この記事をシェアする

FacebookHatena blogX

関連記事