Serverless Framework × TypeScript のローカル開発環境構築メモ(Serverless Offline編)[今回]
Serverless Framework × TypeScript のローカル開発環境構築メモ(DynamoDB Local 編)
Serverless Framework × TypeScript のローカル開発環境構築メモ(S3 Local編)
Serverless Frameworkとは、サーバーレスアーキテクチャでAPIを作る際に面倒なインフラ構築をコマンド一発でやってくれる優れもののフレームワークです!今回はこのServerless Frameworkをローカルで動かせるプラグインServerless Offineを使ってローカル開発できるようにしていきましょう。
#Severless Frameworkの環境構築
まず、serverless frameworkをインストールし、typescriptテンプレートを使ってビルドします。詳しくはリンク先を参照ください。
$ 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や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です。公式に従ってインストールしていきましょう。まず、以下のコマンドを実行します。
$ npm install -D serverless-offline
あとは、serverless.ts内のplugins
の最後に"serverless-offline"
を挿入するだけです。
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
を適当に書き換えます。今回は次のように書き換えました。
import { handlerPath } from '@libs/handlerResolver';
export default {
handler: `${handlerPath(__dirname)}/handler.main`,
events: [
{
http: {
method: 'get',
path: 'hoge',
}
}
]
}
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
関数をインポートしてやるだけです。
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で立てていきます!ぜひ最高のエンジニアライフを!!