2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

NestJS をシンプルに初期構築から試す(その2 API Gateway + AWS Lambdaとしてデプロイ)

Last updated at Posted at 2023-08-27

このページは、その2(その1のつづき)です。

記事 ブランチ 内容
その1 feature/example-step1 nest cli
その2 feature/example-step2-sls use serverless framework, sls deploy

将来、1つのdevelop ブランチ にしてしまうかもしれません

Serverless Framework(sls)とAWS Lambda関連の対応

1. パッケージインストール

をもとに
追加でパッケージインストール

$ npm i @vendia/serverless-express aws-lambda
$ npm i -D @types/aws-lambda serverless-offline

2. ファイルserverless.ymlを作成

serverless-nest/serverless.yml
service: serverless-example
frameworkVersion: "3"

plugins:
  - serverless-offline

provider:
  name: aws
  runtime: nodejs18.x

functions:
  main:
    # handler: src/main.handler , srcかdistか、、、AWSでのLambdaの設定に関わる
    handler: dist/main.handler
    events:
      - http:
          method: ANY
          path: /
      - http:
          method: ANY
          path: '{proxy+}'


上記ファイルは一部省略しているため、GitHubリポジトリの情報を参照してください(ブランチ https://github.com/ssugimoto/serverless-framework-nestjs-example/blob/feature/example-step2-sls/serverless.yml

3. src/main.ts を AWS Lambdaのhandler向けに変更

  • AWS Lambdaの作法に合わせる
src/main.ts
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);
};

  • 変更前のsrc/main.tsを記録として残しておいた
src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

4. 上記では、まだ足らないのでServerless frameworkのpluginを導入

引用

Usage with serverless-offline
The plugin integrates very well with serverless-offline to simulate AWS Lambda and AWS API Gateway locally.

Add the plugins to your serverless.yml file and make sure that serverless-plugin-typescript precedes serverless-offline as the order is important:
serverless.ymlファイルにプラグインを追加し、
順番が重要なのでserverless-plugin-typescriptがserverless-offlineより前にあることを確認する
  • plugin の install
npm install -D serverless-plugin-typescript typescript
npm install -D serverless-plugin-optimize
  • 実行するとserverless-offline も合わせてdevDependenciesにインストールされる、typescriptはマイナーバージョンアップ
"devDependencies": {
    "@nestjs/cli": "^10.0.0",
    "@nestjs/schematics": "^10.0.0",
    "@nestjs/testing": "^10.0.0",
    "@types/aws-lambda": "^8.10.119",
    "@types/express": "^4.17.17",
    "@types/jest": "^29.5.2",
    "@types/node": "^20.3.1",
    "@types/supertest": "^2.0.12",
    "@typescript-eslint/eslint-plugin": "^6.0.0",
    "@typescript-eslint/parser": "^6.0.0",
    "eslint": "^8.42.0",
    "eslint-config-prettier": "^9.0.0",
    "eslint-plugin-prettier": "^5.0.0",
    "jest": "^29.5.0",
    "prettier": "^3.0.0",
    "serverless-offline": "^12.0.4",
    "serverless-plugin-typescript": "^2.1.5",
    "source-map-support": "^0.5.21",
    "supertest": "^6.3.3",
    "ts-jest": "^29.1.0",
    "ts-loader": "^9.4.3",
    "ts-node": "^10.9.1",
    "tsconfig-paths": "^4.2.0",
    "typescript": "^5.1.6"
  },

ここまでで、AWSへのデプロイ前までがほぼ完了しています
ここからは、awsへのデプロイとserverless frameworkを使いデプロイします

5. aws profile

aws profile 設定の参考です

リージョンはバージニアリージョンにしていますが、serverless.ymlprovider > region が使われます。

$ cat ~/.aws/config 
[default]
region=us-east-1

$ cat ~/.aws/credentials 
[default]
aws_access_key_id=AK....HE
aws_secret_access_key=....

6. sls ビルド準備

  • serverless.yml の custom serverlessPluginTypescriptでtsconfig.jsonとは別のファイルに変更できたりします
  • ファイル ./tsconfig.json を使う場合はserverlessPluginTypescriptの設定は不要です
serverless.yml
service: serverless-example

frameworkVersion: "3"

plugins:
  - serverless-plugin-typescript
  - serverless-plugin-optimize
  - serverless-offline

custom:
  serverless-offline:
    httpPort: 3000

  serverlessPluginTypescript:
    # tsConfigFileLocation: './tsconfig.lambda.json'
    tsConfigFileLocation: './tsconfig.build.json'
    #tsConfigFileLocation: './tsconfig.json'
  defaultStage: dev
...

7.sls package

  • パッケージングを実施するコマンド(デプロイまではしない)
  • ビルド等の動作確認として使えるコマンドです
  • sls deployの前にパッケージングが正しくできるか確認する時に使えます
$ sls package
Running "serverless" from node_modules

Packaging serverless-example for stage dev (us-east-1)
Compiling with Typescript...
Using local tsconfig.json - ./tsconfig.build.json
Typescript compiled.

✔ Service packaged (25s)

1 deprecation found: run 'serverless doctor' for more details

sls packageが成功するとファイル .serverless/main.zip にパッケージングされたファイルが作成されます

  • main.zipのファイルの中身の一例
main
├── dist
│   ├── app.controller.d.ts
│   ├── app.controller.js
│   ├── app.controller.js.map
│   ├── app.module.d.ts
│   ├── app.module.js
│   ├── app.module.js.map
│   ├── app.service.d.ts
│   ├── app.service.js
│   ├── app.service.js.map
│   ├── main.d.ts
│   ├── main.js
│   └── main.js.map
├── node_modules
│   ├── accepts
│   ├── acorn-node
│   ├── align-text

...

│   ├── @nestjs
│   ├── node-fetch
│   ├── @nuxtjs
│   ├── object-assign
...

│   ├── @vendia
│   └── xtend
└── package.json

  • 無駄なファイルがたくさん、 node_modules もってきてしまっているため、後々の改善ポイントにしておきます

8. sls deploy

  • AWSクラウドへのデプロイを行います
  • sls packageが内部で先に実行されます
  • Cloudformation+S3が使われます

ビルドしてデプロイ

$ nest build && sls deploy
Running "serverless" from node_modules

Deploying serverless-example to stage dev (us-east-1)
Compiling with Typescript...
Using local tsconfig.json - ./tsconfig.build.json
Typescript compiled.

✔ Service deployed to stack serverless-example-dev (88s)

endpoints:
  ANY - https://xxx.execute-api.us-east-1.amazonaws.com/dev/
  ANY - https://xxx.execute-api.us-east-1.amazonaws.com/dev/{proxy+}
functions:
  main: serverless-example-dev-main (25 MB)

Monitor all your API routes with Serverless Console: run "serverless --console"
  • API Gatewayのエンドポイントが生成されます(存在しなければ新規作成、存在していれば更新)
  • API Gatewayのエンドポイントは1つです
  • トリガー元がAPI GatewayのAWS Lambdaが生成されます(存在しなければ新規作成、存在していれば更新)
  • Lambda Functionは複数ではなく、NestJSでのルーティングが使われるためデプロイされるLambdaは1つです
    • Lambda内で対応したメソッドと関数が呼び出されます
Cloudformationの結果

image.png

APIの動作確認

curl https://xxx.execute-api.us-east-1.amazonaws.com/dev/
curl https://xxx.execute-api.us-east-1.amazonaws.com/dev/say

sls deploy オプション

  • --stage : サービスのステージ
  • --force : 強制デプロイ

ここまでで、AWSクラウド側に REST APIを作成できました。認証や認可の対応等を機能追加していくとよくあるWebアプリケーションに近づいていきます。


Serverless Frameworkの知らなかった機能

sls print で Variables、パラメーターを確認できる

serverless.yml

service: serverless-example

..略

custom:
  serverless-offline:
    httpPort: 3000
    stageVariables:
      foo: "bar"
  defaultStage: dev
  environment:
    dev: ${file(./env/site-dev.yml)}
    prod: ${file(./env/site-prod.yml)}

provider:
  name: aws
  runtime: nodejs18.x
  region: us-east-1
  stage: ${opt:stage, self:custom.defaultStage}

のような場合に

$ sls print --stage prod

を実行すると、以下のように確認できる

Running "serverless" from node_modules
service: serverless-example

...略

custom:
  serverless-offline:
    httpPort: 3000
    stageVariables:
      foo: bar
  defaultStage: dev
  environment:
    dev:
      key: sample
    prod:
      key: prod-sample
provider:
  name: aws
  runtime: nodejs18.x
  region: us-east-1
  stage: prod
  versionFunctions: true

serverless-offlineの意図せぬ挙動

ドメインRootへのアクセスの挙動(plugin serverless-offline の挙動)

  • ドメインルートにアクセスすると動作しない、sls offline の問題
  • ルートアクセス用の getHello() に到達しない
  • src/app.controller.ts
app.controller.ts

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    console.log('controller start.');
    return this.appService.getHello();
  }

  @Get('say')
  getSay(): string {
    return 'say.';
  }
}

curl でドメインルートアクセスした場合

挙動その1

>curl http://localhost:3000/
{"currentRoute":"get - /","error":"Serverless-offline: route not found.","existingRoutes":["* - /dev","* - /dev/{proxy*}"],"statusCode":404}

挙動その2

>curl http://localhost:3000/dev/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot GET null</pre>
</body>
</html>

curl でドメインルートに、スラッシュを1つ多くしてアクセスすると getHello() に到達する

>curl http://localhost:3000/dev//
Hello World!
  • curl で API Gatewayのドメインルートにアクセスした場合
>curl https://xxx.execute-api.us-east-1.amazonaws.com/dev/
Hello World!

>curl https://xxx.execute-api.us-east-1.amazonaws.com/dev//
Hello World!

>curl https://xxx.execute-api.us-east-1.amazonaws.com/dev/say
say.

対処というか回避

余談、NestJSをAWSクラウドで動かす場合

  • EC2 で 動かす(dockerコンテナ使っても良いですし、プロセスとしてミドルウェアとしても動かせる)
  • ALB + ECS Fargate で app.listen(3000); のようにWebサーバー相当でリクエストを受ける
  • ✅APIとして API Gateway + AWS Lambdaとして動かす
  • 他には
    • API Gateway + Fargate
    • App Runner もあります
  • AWSだと、Codeシリーズを使いCI/CDできます
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?