AWS SAMでPrismaを使ったAWS Lambdaを動かす

## AWS SAMでLambdaを作成する
まずは、AWS SAM CLIを用いてテンプレート環境を作成します。事前に、AWS SAM CLIのインストールを[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/install-sam-cli.html)に従って行なってください。
準備が完了したら、テンプレートを作成します。
```sh
sam init
```
質問には以下のように答えます。
![スクリーンショット 2023-12-26 16.30.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591069/9a528524-63f0-1fd4-374f-f5478d718feb.png)
以下のようにアプリケーションが生成されたら準備完了です。
![スクリーンショット 2023-12-26 16.30.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591069/8155c438-e306-5ea5-4e60-a0d002badcd4.png)
`template.yaml`、`samconfig.toml`はLambdaをデプロイするためのSAMの設定、`hello-world`にはLambdaのコードが記述されています。`events`はテストに利用するLambdaのダミー入力です。

## Prismaを導入する
次に、PrismaをLambdaに導入してローカル環境で利用可能にします。
```sh
npm install prisma --save-dev
```
パッケージを導入したら、Prismaプロジェクトを初期化します。
```sh
npx prisma init
```
`prisma/schema.prisma`と`.env`、`.gitignore`が`hello-world`に生成されます。
`schema.prisma`で利用するデータベースのテーブルのモデル化を行ってください。
そして、Lambdaからデータベースにアクセスするためのクライアントを準備します。
```sh
npm install @prisma/client
```
パッケージの導入と、Prismaクライアントやモデルの情報を扱うためのコードを`node_modules`に生成させます。
```sh
npx prisma generate
```
これで、Lambda内でPrismaを扱うための準備は完了です。

最後にPrismaをモックしてテストできるようにします。
モックしやすいように、アプリ全体でPrisma Clientをインスタンス化した1つのものを利用するようはシングルトンな構成に変更します。
```ts:client.ts
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();
export default prisma;
```
データベースにアクセスするコードはこれを使うように書き換えてください。
そして、テスト環境が設定された後、Prisma Clientをモックするようにします。モックするために利用するライブラリを導入します。
```ts
npm install jest-mock-extended --save-dev
```
次に、モックを行うコードを記述します。
```ts:tests/singleton.test.ts
import { PrismaClient } from '@prisma/client'
import { mockDeep, mockReset, DeepMockProxy } from 'jest-mock-extended'

import prisma from './client'

jest.mock('./client', () => ({
  __esModule: true,
  default: mockDeep<PrismaClient>(),
}))

beforeEach(() => {
  mockReset(prismaMock)
})

export const prismaMock = prisma as unknown as DeepMockProxy<PrismaClient>
```
このファイルが呼ばれた後にPrisma Clientを呼び出すと代わりに`prismaMock`を呼び出すようになります。さらに、テストコードでは`prismaMock`で各メソッドの戻り値の設定などを行えます。
Jestの設定で`setupFilesAfterEnv`を設定することで、このファイルをテスト環境が設定された後に呼び出させます。
```ts:jest.config.ts
export default {
    transform: {
        '^.+\\.ts?$': 'ts-jest',
    },
    clearMocks: true,
    collectCoverage: true,
    coverageDirectory: 'coverage',
    coverageProvider: 'v8',
    testMatch: ['**/tests/unit/*.test.ts'],
    setupFilesAfterEnv: ['<rootDir>/singleton.ts'],
};
```

## SAMの設定を変更する
最後にこれまでのコードをSAMからAWS Lambdaに上げ利用できるようにします。
まずは、`prisma/schema.prisma`と`node_modules/.prisma/client/libquery_engine-rhel-openssl-1.0.x.so.node`をAWSへアップロードするバンドルされたコードに含めるためコード内で呼び出すようにします。
```ts:client.ts
import { PrismaClient } from '@prisma/client';
import schema from '@/prisma/schema.prisma';
import x from '@/node_modules/.prisma/client/libquery_engine-rhel-openssl-1.0.x.so.node';

if (process.env.NODE_ENV !== 'production') {
  console.debug(schema, x);
}
const prisma = new PrismaClient();
export default prisma;
```
`schema.prisma`の`binaryTargets`をそれに合わせて修正してください。
```ts:schema.prisma
generator client {
  provider      = "prisma-client-js"
  binaryTargets = ["native", "rhel-openssl-1.0.x"]
}
```
この変更は必ず呼び出されるところであればどこでも問題ないです。
最後に`template.yaml`の設定を変更します。
```yaml
HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-world/
      Handler: app.lambdaHandler
      Runtime: nodejs20.x
      Architectures:
        - x86_64
      Environment:
        Variables:
          DATABASE_URL: xxx
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get
    Metadata:
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Target: "es2020"
        Sourcemap: true
        EntryPoints: 
        - app.ts
        Loader:
          - .prisma=file
          - .so.node=file
        AssetNames: '[name]'
```
`DATABASE_URL`を渡すようにして、esbuildの設定の要素に`Loader`と`AssetNames`を追加しました。
`DATABASE_URL`に渡す`connection_limit`の値は[こちら](https://github.com/prisma/docs/issues/667)を参考して下さい。

これで準備完了です。`sam build`して`sam deploy`すればPrismaを使ったLambdaを作成できます。