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

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

[Serverless Framework](https://www.serverless.com/)とは、サーバーレスアーキテクチャでAPIを作る際に面倒なインフラ構築をコマンド一発でやってくれる優れもののフレームワークです！今回はこのServerless Frameworkをローカルで動かせるプラグイン[Serverless Offine](https://www.serverless.com/plugins/serverless-offline)を使ってローカル開発できるようにしていきましょう。

#Severless Frameworkの環境構築
まず、serverless frameworkをインストールし、typescriptテンプレートを使ってビルドします。詳しくは[リンク先](https://www.serverless.com/)を参照ください。

````
$ npm install -g serverless
$ serverless create --template aws-nodejs-typescript --path api
````
この時のディレクトリ構成は次のようになっています。

````
api
├── README.md
├── package.json
├── serverless.ts
├── src
│   ├── functions
│   │   ├── hello
│   │   │   ├── handler.ts
│   │   │   ├── index.ts
│   │   │   ├── mock.json
│   │   │   └── schema.ts
│   │   └── index.ts
│   └── libs
│       ├── apiGateway.ts
│       ├── handlerResolver.ts
│       └── lambda.ts
├── tsconfig.json
└── tsconfig.paths.json
````
serverless.tsがテンプレートファイルとなっており、このテンプレートではデフォルトで[middy](https://www.npmjs.com/package/middy)や[json-schema-to-ts](https://www.npmjs.com/package/json-schema-to-ts)といったプラグインが入っています。そのため、関数を書く際は、内部のビジネスロジックとAPIのインターフェイスにのみ注力すれば良くなります。ただし、`application/json`以外のContent Typeの場合は修正が必要になります。

`functions`ディレクトリがLambda関数のソースコードを書くディレクトリです。`hello`デレクトリを見ると、`index.ts`でLambda関数の設定を書き、`handler.ts`で実際のハンドラーを書く形になっています。`schema.ts`ではhttpリクエストのスキーマを定義しており、json-schema-to-tsでtype化してハンドラの型定義に使っています。実際に次のコマンドで関数をローカルで実行できます。

````
$ npx sls invoke local -f hello --path ./src/functions/hello/mock.json
````

#Serverless Offlineの導入
前述の`invoke local`は関数を実行するだけだったので、フロントエンドとの結合をテストするなどでAPIを立てる必要がある場合にはプラグインを導入する必要があります。そのためにローカルAPIを立ててくれるプラグインが[Serverless Offline](https://www.serverless.com/plugins/serverless-offline)です。公式に従ってインストールしていきましょう。まず、以下のコマンドを実行します。

````
$ npm install -D serverless-offline
````
あとは、serverless.ts内の`plugins`の最後に`"serverless-offline"`を挿入するだけです。

````serverless.ts
  plugins: ["serverless-esbuild", "serverless-offline"],
````
ここまでできたら、次のコマンドでローカルAPIを立ててみましょう。

````
$ npx sls offline start
````
次のようなメッセージが出ていれば、`localhost:3000`にパス`/dev/hello`のAPIが立っていることになります。

````
   ┌─────────────────────────────────────────────────────────────────────────┐
   │                                                                         │
   │   POST | http://localhost:3000/dev/hello                                │
   │   POST | http://localhost:3000/2015-03-31/functions/hello/invocations   │
   │                                                                         │
   └─────────────────────────────────────────────────────────────────────────┘

offline: [HTTP] server ready: http://localhost:3000 🚀
offline: 
offline: Enter "rp" to replay the last request
^Coffline: Got SIGINT signal. Offline Halting...
offline: Halting offline server
````
次のコマンドで接続確認してみましょう。`name`は各自の名前に変えてくださいね。

````
$ curl -X POST -H "Content-Type: application/json" -d "{\"name\": \"Utan\"}" localhost:3000/dev/hello
````
何もコードをいじってなければ次のレスポンスが返ってくるはずです。もちろん`name`は各自の名前で。

````
{"message":"Hello Utan, welcome to the exciting Serverless world!","event":{"body":{"name":"Utan"},"headers":{"Host":"localhost:3000","User-Agent":"curl/7.77.0","Accept":"*/*","Content-Type":"application/json","Content-Length":"16"},"httpMethod":"POST","isBase64Encoded":false,"multiValueHeaders":{"Host":["localhost:3000"],"User-Agent":["curl/7.77.0"],"Accept":["*/*"],"Content-Type":["application/json"],"Content-Length":["16"]},"multiValueQueryStringParameters":null,"path":"/hello","pathParameters":null,"queryStringParameters":null,"requestContext":{"accountId":"offlineContext_accountId","apiId":"offlineContext_apiId","authorizer":{"principalId":"offlineContext_authorizer_principalId"},"domainName":"offlineContext_domainName","domainPrefix":"offlineContext_domainPrefix","extendedRequestId":"ckydl1ztx0003r28z3ftnejyj","httpMethod":"POST","identity":{"accessKey":null,"accountId":"offlineContext_accountId","apiKey":"offlineContext_apiKey","apiKeyId":"offlineContext_apiKeyId","caller":"offlineContext_caller","cognitoAuthenticationProvider":"offlineContext_cognitoAuthenticationProvider","cognitoAuthenticationType":"offlineContext_cognitoAuthenticationType","cognitoIdentityId":"offlineContext_cognitoIdentityId","cognitoIdentityPoolId":"offlineContext_cognitoIdentityPoolId","principalOrgId":null,"sourceIp":"127.0.0.1","user":"offlineContext_user","userAgent":"curl/7.77.0","userArn":"offlineContext_userArn"},"path":"/hello","protocol":"HTTP/1.1","requestId":"ckydl1ztx0004r28zdrcjbjdq","requestTime":"14/Jan/2022:08:08:26 +0900","requestTimeEpoch":1642115306660,"resourceId":"offlineContext_resourceId","resourcePath":"/dev/hello","stage":"dev"},"resource":"/hello","rawBody":"{\"name\": \"Utan\"}"}}
````

#Lambda関数を足してみる
以上で、基本的なローカルAPI開発環境は整いました！あとは`functions`ディレクトリにどんどん関数を足していくだけですね。そのやり方をメモしておきます。

関数を足す際には、まず次のように`functions`ディレクトリに新しいディレクトリを足します。今回は`hoge`ディレクトリを足しました。

````
api/src/functions
├── hello
│   ├── handler.ts
│   ├── index.ts
│   ├── mock.json
│   └── schema.ts
├── hoge
│   ├── handler.ts
│   ├── index.ts
│   ├── mock.json
│   └── schema.ts
└── index.ts
````
次に、`hoge/index.ts, handler.ts`を適当に書き換えます。今回は次のように書き換えました。

````hoge/index.ts
import { handlerPath } from '@libs/handlerResolver';

export default {
  handler: `${handlerPath(__dirname)}/handler.main`,
  events: [
    {
      http: {
        method: 'get',
        path: 'hoge',
      }
    }
  ]
}
````
````hoge/handler.ts
import type { ValidatedEventAPIGatewayProxyEvent } from "@libs/apiGateway";
import { formatJSONResponse } from "@libs/apiGateway";
import { middyfy } from "@libs/lambda";

const handler: ValidatedEventAPIGatewayProxyEvent<{}> = async (event) => {
  return formatJSONResponse(200, {
    message: `Hoge !!`,
    event,
  });
};

export const main = middyfy(handler);
````

あとは、`serverless.ts`の`functions`にエクスポートした`hoge`関数をインポートしてやるだけです。

````serverless.ts
  functions: { hello, hoge },
````
ここまでできたら`npx sls offline start`で再度ローカルAPIを立てましょう。次のように`hoge`のパスが追加されているはずです。

````
   ┌─────────────────────────────────────────────────────────────────────────┐
   │                                                                         │
   │   POST | http://localhost:3000/dev/hello                                │
   │   POST | http://localhost:3000/2015-03-31/functions/hello/invocations   │
   │   GET  | http://localhost:3000/dev/hoge                                 │
   │   POST | http://localhost:3000/2015-03-31/functions/hoge/invocations    │
   │                                                                         │
   └─────────────────────────────────────────────────────────────────────────┘

offline: [HTTP] server ready: http://localhost:3000 🚀
offline: 
offline: Enter "rp" to replay the last request
````
実際に`GET`リクエストを送って確認してみましょう。

````
$ curl -X GET localhost:3000/dev/hoge
````
次のようなレスポンスが帰ってくるはずです。

````
{"message":"Hoge !!","event":{"body":null,"headers":{"Host":"localhost:3000","User-Agent":"curl/7.77.0","Accept":"*/*"},"httpMethod":"GET","isBase64Encoded":false,"multiValueHeaders":{"Host":["localhost:3000"],"User-Agent":["curl/7.77.0"],"Accept":["*/*"]},"multiValueQueryStringParameters":null,"path":"/hoge","pathParameters":null,"queryStringParameters":null,"requestContext":{"accountId":"offlineContext_accountId","apiId":"offlineContext_apiId","authorizer":{"principalId":"offlineContext_authorizer_principalId"},"domainName":"offlineContext_domainName","domainPrefix":"offlineContext_domainPrefix","extendedRequestId":"ckydluco000000w8z1th29e49","httpMethod":"GET","identity":{"accessKey":null,"accountId":"offlineContext_accountId","apiKey":"offlineContext_apiKey","apiKeyId":"offlineContext_apiKeyId","caller":"offlineContext_caller","cognitoAuthenticationProvider":"offlineContext_cognitoAuthenticationProvider","cognitoAuthenticationType":"offlineContext_cognitoAuthenticationType","cognitoIdentityId":"offlineContext_cognitoIdentityId","cognitoIdentityPoolId":"offlineContext_cognitoIdentityPoolId","principalOrgId":null,"sourceIp":"127.0.0.1","user":"offlineContext_user","userAgent":"curl/7.77.0","userArn":"offlineContext_userArn"},"path":"/hoge","protocol":"HTTP/1.1","requestId":"ckydluco000010w8zh9agf16c","requestTime":"14/Jan/2022:08:30:29 +0900","requestTimeEpoch":1642116629657,"resourceId":"offlineContext_resourceId","resourcePath":"/dev/hoge","stage":"dev"},"resource":"/hoge"}}
````

#終わりに
ここまででServerless FrameworkでサーバーレスAPIのローカル開発環境の基本的な部分が整ったかと思います。あとはどんどん関数を足してリッチなAPIにしてくだけですね！！次回はDynamoDBのローカル開発環境をServerlessで立てていきます！ぜひ最高のエンジニアライフを！！