NestJSをLambda上に乗せたい話
NestJSをLambdaに載せたい
いくつかの批判的意見はありつつもメリットが上回りそうかなと思っているので簡単にメリデメと動かし方について整理しておきます。最悪ECSへの移植することになるかもしれませんが、NestJSで作っておけば比較的容易に済むかと。
概要
先達はいるのか
いっぱいいます。一見奇抜なアイデアにも見えますが、一定の有効性は保証できそうです。
- @vendia/serverless-expressでExpressアプリをAPIGateway+Lambdaにデプロイしてみた
- NestJS で作成したRESTfull APIをAWS Lambda + API Gatewayで構成する
- 「NestJS」をAWS Lambda + API Gatewayで動かす
- Ionic/Angularと相性抜群のNodeフレームワーク「NestJS」をLambdaで公開する方法
- NestJS serverless application on AWS with DynamoDB
想定されるメリット
想定されるメリットを整理しておきます。このあたりが挙げられるでしょう。
- Lambda関数が一つになるので管理やデプロイが手軽
- GETやPOSTなどのHTTPメソッドの分岐をLambda関数内ではなく、フレームワークのルーティング機能を活用して実現できる(コードの見通しが良くなる(かも)
- Express用のプラグインが使える(エコシステムが活かせる)
想定されるデメリット
また、デメリットや批判としては以下が挙げられますが、一応の反論もできそうです。
- Hackyな構成ではあるし、ECSに載せるほうが良いのでは?同時実行数の問題もある。
-> Lambdaであればゼロスケール できるのでコストメリットがある
-> フレームワーク使って実装するので、ECSへの移行も比較的容易に可能なはずなので、最悪それで対応できる。 - 関数ごとにAWS上でポリシー設定が出来ないのはイマイチでは?
-> Guardなどでロール別認可処理を加えれば、要件次第だが一定カバーは可能かと。
NestJS公式はなんと言っている?
推奨も否定もしていなそうだが、動かし方やコールドスタート対策などは書いている
動かし方
エッセンスだけメモしておきます。
必要なライブラリ
本番でも必要なものは以下になります。
@vendia/serverless-express
aws-lambda
また、開発時に便利なので以下を入れとくと良いかと(AWS SAM使っても別に変わらないのでそこは好みで)
serverless
-
serverless-offline
: ローカルでのテスト用
実装や設定
公式に乗っ取ればそのまま動かせるので基本的にはそれを見てください。一応多少端折りつつ紹介だけはしておきます。
serverless.yaml
に以下の設定の追加が必要となります(一部略)。ポイントはmethod
にANY
, path
に{proxy+}
を指定して、あらゆるリクエストを受け入れられるようにするところです。
...
plugins:
- serverless-offline
functions:
XXXX:
handler: dist/main.handler
events:
- http:
method: ANY
path: /
- http:
method: ANY
path: '{proxy+}'
以下は完全に公式ドキュメントからのコピペですが、以下のようにmain.ts
にhandler
関数を追加する必要があります(上のdist/main.handelr
に該当するものですね)。
import { NestFactory } from '@nestjs/core';
import serverlessExpress from '@vendia/serverless-express';
import { Callback, Context, Handler } from 'aws-lambda';
import { AppModule } from './app.module';
let server: Handler;
async function bootstrap(): Promise<Handler> {
const app = await NestFactory.create(AppModule);
await app.init();
const expressApp = app.getHttpAdapter().getInstance();
return serverlessExpress({ app: expressApp });
}
export const handler: Handler = async (
event: any,
context: Context,
callback: Callback,
) => {
server = server ?? (await bootstrap());
return server(event, context, callback);
};
tsconfig.json
に以下の追加が必要です。
(参考: TypeScriptのesModuleinteropフラグを設定してCommonJSモジュールを実行可能とする)
"esModuleInterop": true
あとはビルドして $npx serverless offline
とすればlocalhost:3000/dev
のエンドポイントで挙動が確認できます($ sls deploy
でsandbox環境へデプロイしてしまってもいいかと)
結論
とても有効なはず。
Discussion