Serverless Framework × TypeScript のローカル開発環境構築メモ（S3 Local編）

[Serverless Framework × TypeScript のローカル開発環境構築メモ（Serverless Offline編）](https://qiita.com/0taUtan/items/aca988de4b0a53244950)
[Serverless Framework × TypeScript のローカル開発環境構築メモ（DynamoDB Local 編）](https://qiita.com/0taUtan/items/9fdbc791fc936d6380e5)
Serverless Framework × TypeScript のローカル開発環境構築メモ（S3 Local編）[今回]

[前回](https://qiita.com/0taUtan/items/9fdbc791fc936d6380e5)は[Serverless Framework](https://www.serverless.com)のプラグイン[Serverless Dynamodb Local](https://www.serverless.com/plugins/serverless-dynamodb-local)を使ってサーバーレス開発をローカルで行うための方法を紹介しました。今回はさらにS3もローカルで動かせるようにしましょう。そこで登場するのが[Serverless S3 Local](https://www.serverless.com/plugins/serverless-s3-local)です。

# Serverless S3 Local のインストール
まず、前回の最後で、次のような感じのディレクトリ構成になっていたかと思います。

````
backends
├── api
│   ├── README.md
│   ├── node_modules
│   ├── package-lock.json
│   ├── package.json
│   ├── serverless.ts
│   ├── src
│   ├── tsconfig.json
│   └── tsconfig.paths.json
└── resources
│   ├── README.md
│   ├── node_modules
│   ├── package-lock.json
│   ├── package.json
│   ├── serverless.ts
│   ├── src
│   ├── tsconfig.json
│   └── tsconfig.paths.json
````
`resources`ディレクトリ内でDynamoDB Localの時と同じような順番でインストールしていきます。まずは`npm install`からです。今回はServerless Offlineも必要になってくるので、ついでにインストールしちゃいます。

````
$ cd resources
$ npm install -D serverless-s3-local　serverless-offline
````
次に、serverless.tsの`plugins`に以下のようにプラグインを足します。前回のやつに追記したので`serverless-dynamodb-local`が入っていますが、使わない方は消しちゃってくださいね。

````serverless.ts
const serverlessConfiguration: AWS = {
  ...
  plugins: [
    "serverless-esbuild",
    "serverless-dynamodb-local",
    "serverless-s3-local",
    "serverless-offline",
  ],
  ...
}
````
ここで、プラグインを環境にインストールしちゃいます。（なお、今回はDynamoDB Localの場合と違ってserverless.tsに追記する前に以下コマンドを走らせられます。）

````
$ npx sls plugin install --name serverless-s3-local
````
これでインストールは完了です。

# Serverless S3 Local の起動
まずは`serverless.ts`にリソースを追加します。とりあえず何のオプションも付けずにバケットを作成します。

````serverless.ts
const serverlessConfiguration: AWS = {
  ...
  resources: {
    Resources: {
      ...
      ResourceBucket: {
        Type: "AWS::S3::Bucket",
        Properties: {
          BucketName: "test-bucket",
        },
      },
    },
  },
  ...
}
````
次に、`custom`に`s3`のオプションを追加します。

````serverless.ts
const serverlessConfiguration: AWS = {
  ...
  custom: {
    ...
    s3: {
      host: "localhost",
      port: 4569,
      directory: "./buckets",
    },
  },
}
````
ここまで来ればあとは起動するだけです！なお、以下のコマンドではDynamoDB Localも一緒に起動します。（便利ですね！）

````
$ npx sls offline start
````
以下が表示されて、現在のディレクトリ直下に`buckets`が生成されれば正しく起動されています。

````
Serverless: S3 local started ( port:4569, family: IPv4, address: 127.0.0.1 )
````

なお、`buckets`にファイルを置いただけでは反映されませんのでご注意を。

# 接続確認
まず、sdkをインストールします。(macユーザーの方でCMakeが入ってない方はインストールに失敗します。brewなどでインストールしてください。)

````
$ npm install @aws-sdk/client-s3
````
次にlambda関数を書きます。今回は、[Serverless Offlineの回](https://qiita.com/0taUtan/items/aca988de4b0a53244950)で作ったhogeを流用します。まず、[公式](https://www.serverless.com/plugins/serverless-s3-local)に従ってS3のconfigを書きます。なおSDKのバージョンによって書き方が若干違います。

````functions/hoge/handler.ts
import { S3, S3ClientConfig, PutObjectCommandInput } from "@aws-sdk/client-s3";
const s3Config: S3ClientConfig = {
  forcePathStyle: true,
  credentials: {
    accessKeyId: "S3RVER",
    secretAccessKey: "S3RVER",
  },
  endpoint: "http://localhost:4569",
};
const s3Client = new S3(s3Config);
````
次に、ハンドラを書きます。今回は、リクエストボディでバケットオブジェクトのキーと中身のデータ（文字列）を指定して、そこからバケットにそのまま突っ込むという簡単なAPIを作ってみました。

````functions/hoge/handler.ts
import schema from "./schema";
const handler: ValidatedEventAPIGatewayProxyEvent<typeof schema> = async (
  event
) => {
  const body = event.body;
  const param: PutObjectCommandInput = {
    Bucket: "test-bucket",
    Key: body.key,
    Body: body.data,
  };
  await s3Client.putObject(param);
  return formatJSONResponse({
    message: `Hoge !!`,
    event,
  });
};
````
なお、リクエストボディのスキーマはこんな感じです。

````functions/hoge/schema.ts
export default {
  type: "object",
  properties: {
    key: { type: "string" },
    data: { type: "string" },
  },
  required: ["name"],
} as const;
````

実際に起動してみましょう！以下のコマンドで実行できます。

````
$ cd ../api
$ npx sls invoke local -f hoge --path ./src/functions/hoge/mock.json
````
なお、次のようなモックで起動しました。

````functions/hoge/mock.json
{
  "headers": {
    "Content-Type": "application/json"
  },
  "body": "{\"key\": \"test.txt\", \"data\": \"Hello World!!\"}"
}
````
ステータスコード200が帰ってきたら、実際に`http://localhost:4569/test-bucket/test.txt`へアクセスしてバケットにアップロードされているかも確認しましょう。次のようなテキストファイルがダウンロードされたら成功です！

````test.txt
Hello World!!
````

#まとめ
今回はS3をローカルで動かせる環境を構築してみました。手順は以下の通りです。

1. `serverless create`でサーバーレス環境をインストール
1. `npm install`でserverless-s3-localとserverless-offlineをインストール
1. `serverless.ts`にプラグインを記載して、`npx sls plugin install`で環境にS3 Localをインストール
1. `serverless.ts`にリソースを追加して、`custom`にS3 Localの設定を記載
1. `npx sls offline start`で起動
1. 接続確認

このようにしてS3と[前回](https://qiita.com/0taUtan/items/9fdbc791fc936d6380e5)のDynamoDBを立てれば、基本的なサーバーレス構成がローカルで開発できちゃいます！めちゃめちゃ便利すぎません？？

今回でServerless Frameworkのローカル開発編はおしまいです。機会があればデプロイ編も書こうと思います。では、今日も最高のエンジニアライフを！