「NestJS」をAWS Lambda + API Gatewayで動かす

どうも!大阪オフィスの西村祐二です。

個人的にフロントエンドはAngularを使ってよく開発しています。

AngularはTypeScriptがデフォルトなので、APIのレスポンスを型定義するのですが、サーバーサイドもTypeScriptにして型定義を共有できたら良さそうだなと思っていました。

そこで最近、TypeScript製Node.jsフルスタックフレームワーク「NestJS」を調べています。

通常であれば、ECSやFargeteで動かすのが筋かと思いますが、Lambdaの機能改善によって、ちょっとした検証環境であれば、Lambdaで動かすこともありなのかなと思うようになってきました。

そこで、今回はその「NestJS」をAWS Lambda + API Gatewayのサーバーレス構成で動かす方法をご紹介したいと思います。

あくまでも、こういうこともできるという一案であり、この構成についてはいろいろ議論や検討の余地があるところかと思いますので、ご考慮いただけると幸いです。

また、どういう構成がいいかなどみんなで議論できると嬉しいです。

環境

  • Node: 12.13.0
  • NestJS: 6.10.1
  • Serverless Framework: 1.57.0

作業概要

Angular同様にNestJSもCLIが提供されていますので、それを使ってプロジェクトを作成します。

LambdaへのデプロイはServerless Frameworkを使ってデプロイします。

node_modulesの容量が大きくなってしまうので、Lambda Layersを利用します。

Lambda Layersへのデプロイを簡単にするためにプラグインの「serverless-layers」を使用します。

試してみる

環境準備

パッケージをインストールします。

$ npm i -g @nestjs/cli
$ npm i -g serverless

NestJSのプロジェクト作成

NestJSのCLIを使ってプロジェクトを作成します。

今回は「serverless-nestjs」としています。

$ nest new serverless-nestjs

? Which package manager would you ❤️  to use? npm

プロジェクトが作成できたら、ローカルで実行しています。

$ cd serverless-nestjs
$ npm run start

ブラウザをひらき、「http://localhost:3000」にアクセスし

Hello World!

と表示されればOKです。

次はNestJSがLambdaで動くようにハンドラーファイルを作成します。

Lambda用のハンドラーファイルを作成

必要なライブラリをインストールします。

$ npm i aws-lambda aws-serverless-express express

API Gatewayからデータを受けとるためのLambdaハンドラーを作成します。

import { Context, Handler } from 'aws-lambda';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Server } from 'http';
import { ExpressAdapter } from '@nestjs/platform-express';
import * as serverless from 'aws-serverless-express';
import * as express from 'express';

let cachedServer: Server;

function bootstrapServer(): Promise<Server> {
  const expressApp = express();
  const adapter = new ExpressAdapter(expressApp);
  return NestFactory.create(AppModule, adapter)
    .then(app => app.enableCors())
    .then(app => app.init())
    .then(() => serverless.createServer(expressApp));
}

export const handler: Handler = (event: any, context: Context) => {
  if (!cachedServer) {
    bootstrapServer().then(server => {
      cachedServer = server;
      return serverless.proxy(server, event, context);
    });
  } else {
    return serverless.proxy(cachedServer, event, context);
  }
};

Serverless Frameworkの設定

プラグインをインストールします。

$ npm i -D serverless-layers

Layerをデプロイするために事前にS3にバケットを作成しておきます。

今回は「nestjs-lambda-layers」というバケットを作成しておきました。

NestJSはビルドコマンドでTypeScriptのファイルをJSに変換し、dist/に出力してくれるので、そのファイルをデプロイパッケージに含めデプロイします。

node_modulesのファイルはserverless-layersを使ってLayerとして紐付けます。

作成するAPI GatewayはAnyとして受け取ったリクエストはすべてLambdaで動くNestJSに渡すようにします。

service: serverless-nestjs

provider:
  name: aws
  runtime: nodejs10.x
  region: ap-northeast-1

plugins:
  - serverless-layers
custom:
  serverless-layers:
    layersDeploymentBucket: nestjs-lambda-layers

package:
  individually: true
  include:
    - dist/**
  exclude:
    - '**'
functions:
  index:
    handler: dist/index.handler
    events:
      - http:
          cors: true
          path: '/'
          method: any
      - http:
          cors: true
          path: '{proxy+}'
          method: any

これで準備完了です。

デプロイしてみる

下記コマンドを実行して、デプロイしてみましょう。

$ nest build && sls deploy

Serverless: [LayersPlugin]: Downloading package.json from bucket...
Serverless: [LayersPlugin]: package.json does not exists at bucket...
Serverless: [LayersPlugin]: Dependencies has changed! Re-installing...
npm WARN serverless-nestjs@0.0.1 No description
npm WARN serverless-nestjs@0.0.1 No repository field.


> @nestjs/core@6.10.1 postinstall /Users/yuji/study/nestjs/serverless-nestjs/.serverless/layers/nodejs/node_modules/@nestjs/core
> opencollective || exit 0

added 161 packages from 151 contributors and audited 885260 packages in 6.078s
found 0 vulnerabilities


Serverless: [LayersPlugin]: Created layer package /Users/yuji/study/nestjs/serverless-nestjs/.serverless/serverless-nestjs-dev.zip (13.6 MB)
Serverless: [LayersPlugin]: Uploading layer package...
Serverless: [LayersPlugin]: OK...
Serverless: [LayersPlugin]: New layer version published...
Serverless: [LayersPlugin]: Uploading remote package.json...
Serverless: [LayersPlugin]: OK...
Serverless: [LayersPlugin]: Associating layers...
Serverless: [LayersPlugin]: function.index - arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxxxxxxx:layer:serverless-nestjs-dev:2
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
........
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service index.zip file to S3 (42.58 KB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
.......................................
Serverless: Stack update finished...
Service Information
service: serverless-nestjs
stage: dev
region: ap-northeast-1
stack: serverless-nestjs-dev
resources: 14
api keys:
  None
endpoints:
  ANY - https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/
  ANY - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/{proxy+}
functions:
  index: serverless-nestjs-dev-index
layers:
  None
Serverless: [LayersPlugin]: function.index = layers.arn:aws:lambda:ap-northeast-1:xxxxxxxx:layer:serverless-nestjs-dev:2
Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.

上記のようなログがでれば成功です!

これで、LambdaでNestJSが動く、API Gateway+Lambdaの構成ができました!簡単ですね。

デプロイしたAPIを叩いてみる

上記でAPI Gateway+Lambdaの構成ができたので実際に動かしてみたいと思います。

ログ出力のendpointsのところに出力されたURLにブラウザからアクセスしてみましょう。

するとローカル実行で確認したのと同じように「Hello World!」が出力されるかと思います!やりましたね!

さいごに

「NestJS」をAWS Lambda + API Gatewayの構成で動かしてみました。

「NestJS」はAngularのように開発できたり、CLIでローカル実行やビルド、テストなどが実行できたりと、いろんな機能が予め用意されているので、環境構築に手間がかからず、すぐに開発に取りかかれて良いです。

さらに、Serverless Frameworkを使えば、簡単に「NestJS」が動くAPI Gateway+Lambdaの構成を構築できます。

次はAngular+NestJSで実践的なものを作ってみたいと思います。

誰かの参考になれば幸いです。

参考サイト

https://qiita.com/rdlabo/items/57ab04274855b35196bb