概要
NestJSをWindows環境にインストールするところから、Dockerファイルに固めてコンテナ環境上で動作させるところまでの手順を忘備録代わりに残します。
環境は下記のとおりです
環境 | バージョン |
---|---|
OS | Windows 11 Home 22H2 |
nodeバージョン | v18.13.0 |
npm バージョン | 8.19.3 |
エディタ | Visual Studio Code (*.editorconfigとかフォーマッタとか便利に使う設定を行う) |
EoLスケジュール・サポートされる機能の確認
node 18.xは2025/4/30までサポートが行われる予定です。
NodeJSのリリーススケジュールは以下のページに記載があります。
https://github.com/nodejs/release#release-schedule
NodeJSのバージョンとサポートされるECMAScriptの機能について下記のページに記載があります。
https://node.green/
環境構築
voltaのインストール
nodeのバージョン管理ツールとして、「volta」をインストールします
https://docs.volta.sh/guide/getting-started
Windows Installationの項目の「download and run the Windows installer 」からインストーラーをダウンロードしてインストールできます。
※他のnodeのバージョン管理ツールが入っている場合は、先に削除する必要があります
node/npmのインストール及び設定
下記コマンドを実行し、node18をインストールします
volta install node@18
以下参考になりそうな記事
- https://blog.frevo-works.co.jp/entry/2022/05/02/163432
- https://link-and-motivation.hatenablog.com/entry/2022/05/20/160000
- https://www.3ryu-engineer.work/windows-volta-nodejs/
下記コマンドでインストールされていることを確認
PS C:\Users\yuuri> node -v
v18.13.0
PS C:\Users\yuuri> npm -v
8.19.3
nestCLIのインストール
下記コマンドでnestCLIをグローバルインストールします
npm i -g @nestjs/cli
nestjsのプロジェクトの作成及び確認
プロジェクトの作成
インストールしたnestCLIを使用して、nestjsのプロジェクトを作成します。
使用するpackage managerを聞かれるので、使用したいものを選んでプロジェクトの作成を行います。
nest new <project-name>
下記ではnpm
を選択しました
nest new nest-sample-project
npm WARN deprecated sourcemap-codec@1.4.8: Please use @jridgewell/sourcemap-codec instead
added 250 packages, and audited 251 packages in 19s
41 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
npm notice
npm notice New major version of npm available! 8.19.3 -> 9.3.1
npm notice Changelog: https://github.com/npm/cli/releases/tag/v9.3.1
npm notice Run npm install -g npm@9.3.1 to update!
npm notice
PS C:\Users\yuuri\sorairo-architecture> nest new nest-sample-project
⚡ We will scaffold your app in a few seconds..
? Which package manager would you ❤️ to use? npm
CREATE nest-sample-project/.eslintrc.js (663 bytes)
CREATE nest-sample-project/.prettierrc (51 bytes)
CREATE nest-sample-project/nest-cli.json (171 bytes)
CREATE nest-sample-project/package.json (1950 bytes)
CREATE nest-sample-project/README.md (3340 bytes)
CREATE nest-sample-project/tsconfig.build.json (97 bytes)
CREATE nest-sample-project/tsconfig.json (546 bytes)
CREATE nest-sample-project/src/app.controller.spec.ts (617 bytes)
CREATE nest-sample-project/src/app.controller.ts (274 bytes)
CREATE nest-sample-project/src/app.module.ts (249 bytes)
CREATE nest-sample-project/src/app.service.ts (142 bytes)
CREATE nest-sample-project/src/main.ts (208 bytes)
CREATE nest-sample-project/test/app.e2e-spec.ts (630 bytes)
CREATE nest-sample-project/test/jest-e2e.json (183 bytes)
✔ Installation in progress... ☕
🚀 Successfully created project nest-sample-project
👉 Get started with the following commands:
$ cd nest-sample-project
$ npm run start
Thanks for installing Nest 🙏
Please consider donating to our open collective
to help us maintain this package.
🍷 Donate: https://opencollective.com/nest
設定したプロジェクト名と同名のフォルダが作成され、その中にnestjsのプロジェクトが作成されていることが確認できます
ls .\nest-sample-project\
ディレクトリ: C:\Users\yuuri\sorairo-architecture\nest-sample-project
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2023/01/21 2:32 node_modules
d----- 2023/01/21 2:31 src
d----- 2023/01/21 2:31 test
-a---- 2023/01/21 2:31 663 .eslintrc.js
-a---- 2023/01/21 2:32 391 .gitignore
-a---- 2023/01/21 2:31 51 .prettierrc
-a---- 2023/01/21 2:31 171 nest-cli.json
-a---- 2023/01/21 2:32 554915 package-lock.json
-a---- 2023/01/21 2:31 1950 package.json
-a---- 2023/01/21 2:31 3340 README.md
-a---- 2023/01/21 2:31 97 tsconfig.build.json
-a---- 2023/01/21 2:31 546 tsconfig.json
アプリケーションの起動
プロジェクトのフォルダに移動し、下記コマンドを実行することでアプリケーションを起動します。
下記コマンドで起動した場合、ファイルに変更を加えた場合自動的にWebサーバーが再起動されます。
npm run start:dev
package.jsonのscriptの箇所には下記のものが定義されています(一部抜粋しています)
開発環境では npm run start:dev
もしくは npm run start:debug
を利用すればよさそうです
"build": "nest build",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
Voltaでのnodeバージョンの固定
プロジェクトのフォルダ内で下記コマンドを実行し、このプロジェクトで利用するnodeのバージョンを固定します。
volta pin node@18
volta pin npm@8
package.json内へに下記のように使用するnodeのバージョンが追記されます。
"volta": {
"node": "18.13.0",
"npm": "8.19.3"
}
エディタ絡みの環境設定
Visual Studio Codeにプラグインを追加し、.editorconfigによるエディタ設定の適用と保存時のフォーマッタによるコード整形を実行できるようにします。
ぼっちで開発するときはあまりきにしないのですが、複数人で開発するときはこのあたりが入っていないとgit関連の操作やレビューで大変なことになるので設定します。
.editorconfig
EditorConfig for VS CodeプラグインをVSCodeへ追加します
その後プロジェクトルートに.editorconfig
ファイルを作成します。
root = true
[*]
# インデント時のスタイル設定(tab/space)
indent_style = space
# インデント時のサイズ設定
indent_size = 2
# 改行コード設定(lf/cr/crlf)
end_of_line = lf
# 文字コード設定(latin1/utf-8/utf-8-bom/utf-16be/utf-16le/...)
charset = utf-8
# 文末スペースの設定(true=削除)
trim_trailing_whitespace = true
# 最終行の改行設定(true=改行あり)
insert_final_newline = true
以上で、ファイルを操作する際に.editorconfig
の設定がエディタに反映されるようになります。
参考: https://rfs.jp/sb/vsc/editorconfig.html
フォーマッタなどの設定
デフォルト設定
デフォルトでprettierの設定ファイルである.prettierrc
が作成されます。
{
"singleQuote": true,
"trailingComma": "all"
}
またpackage.json
のscriptsのところへ下記設定が設定されています。
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
そのため、npm run format
コマンドを実行することでコードのフォーマットを行うことができます。
npm run format
> nest-sample-project@0.0.1 format
> prettier --write "src/**/*.ts" "test/**/*.ts"
src\app.controller.spec.ts 200ms
src\app.controller.ts 12ms
src\app.module.ts 7ms
src\app.service.ts 6ms
src\main.ts 4ms
test\app.e2e-spec.ts 11ms
VS Codeへプラグインの追加及び設定
ただ、毎回手動でフォーマットを実行するのは面倒くさいというかそのうちやらなくなるので、下記プラグインを追加しファイル保存時に自動的にフォーマッターが動作するようにします
ファイル>ユーザー設定>設定 からVScodeの設定画面を開き、左側のメニューからテキスト エディター>書式設定を選択します
Format On Paste
, Format On Save
, Format On Type
のチェックボックスへ要件に応じてチェックを入れます。
ファイル保存時にフォーマッタを動作させる場合、Format On Paste
にチェックを入れます。
その後、Default Formatter
の項目で、Prettier
をデフォルトのフォーマッタとして設定します。
これで、ファイル保存時などにpretterによるコードフォーマットが動作します。
.editorconfigの値はprettierからも参照(一部非対応で参照されないものもあるようですが)され、これに従ってフォーマットが行われるようです。
Linterの設定
デフォルト設定
デフォルトでeslinterの設定ファイルである.eslintrc.js
が作成されます。
下記コマンドを実行することで、linterを動作させることができます(自動的にコードが修正されます)。
npm run lint
> nest-sample-project@0.0.1 lint
> eslint "{src,apps,libs,test}/**/*.ts" --fix
VS Codeへプラグインの追加及び設定
下記プラグインをVS Codeに追加します。
https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint
プラグインを追加することで、linterによる構文検査結果をVScode上で確認することができます
参考:
環境変数及び設定ファイルの作成
- ローカル環境での実行は.envファイルを読み込む
- その他環境での実行時は、環境変数から設定を読み込む
形で実装していきます。
configurationモジュールの追加
下記コマンドでconfigurationモジュールを追加します
npm i --save @nestjs/config
app.module.ts
内でconfigurationモジュールを読み込むよう設定を追加します
この例ではnest内のすべてのモジュールから利用できるように、isGlobal:true
を設定しています。
https://docs.nestjs.com/techniques/configuration#use-module-globally
またprocess.env
へのアクセス(configurationモジュールは内部でdotenvを利用しています)が遅くなる場合があるそうなので、cache: true
を設定しています。
https://docs.nestjs.com/techniques/configuration#cache-environment-variables
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config'; //追加
@Module({
imports: [
ConfigModule.forRoot({ //追加
isGlobal: true, //追加
cache: true, //追加
}), //追加
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
設定値の読み取り
動作確認用に.envファイルを設定しました。
ENVIRONMENT=local
app.service.ts
で、configServiceを読み込み、.get('<環境変数名>')
メソッドを使うことで設定値を読み込むことができます。
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; //追加
@Injectable()
export class AppService {
private valueFromEnvFile: string; //追加
constructor(private configService: ConfigService) {
this.valueFromEnvFile = this.configService.get<string>('ENVIRONMENT'); //追加
}
getHello(): string {
return 'valueFromEnvfile: ' + this.valueFromEnvFile; //追加
}
}
下記コマンドで開発用サーバーを起動します
npm run start:dev
localhost:3000
へアクセスして動作確認を行います。
.env
ファイルの中身が表示されることが確認できました。
環境変数の利用
.envファイルと環境変数で同じ名前が設定されていた場合、環境変数の設定が優先して利用されます。
そのためローカル環境以外の環境では、環境変数を設定しておくことで.env
がなくても環境変数の値を参照して動作させる、もしくは.env
の値を環境変数で設定値を上書きして動作させることができます。
詳細はあとの章に記載しますが、例えばdocker-composeで環境変数を設定することで、.env
から値を読み出すのと同様の手順で環境変数の値をプログラム内から使用することができます。
version: '3'
services:
nest-sample-project:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./src:/usr/src/app/src
ports:
- 3001:3000
environment:
- ENVIRONMENT=docker #環境変数の設定
command: ['npm', 'run', 'start:dev']
参考:
Docker環境の整備
- 開発環境用
- Dockerfile.dev
- docker-compose
- 本番環境用
- Dockerfile
を用意します。
開発環境では、他のDockerイメージと連携させて動作させるような環境の開発環境構築を想定しています。(単体で動かすだけであればnode run start:dev
すればいいかと思います)
本番環境はApp RunnerやCloud Run、K8sなどで動作させることを想定しています。
.dockerignore
の準備
プロジェクトのルートディレクトリに下記ファイルを用意します
node_modules
npm-debug.log
dist
Dockerfile
Dockerfile.dev
docker-compose.yml
.env
.editorconfig
.eslintrc.js
.prettierrc
README.md
.git
.gitignore
.dockerignore
開発環境の構築
参考: https://nodejs.org/ja/docs/guides/nodejs-docker-webapp/
プロジェクトルートにそれぞれファイルを配置します。
FROM node:18
ENV NODE_ENV development
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run","start:dev"]
version: '3'
services:
nest-sample-project:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- ./src:/usr/src/app/src
ports:
- 3000:3000
environment:
- ENVIRONMENT=docker
command: ['npm', 'run', 'start:dev']
ビルド
下記コマンドでDockerイメージのビルドを実行します
docker-compose build
起動
下記コマンドでdocker-composeで定義した開発環境をバックグラウンドで起動します。
docker-compose up -d
ログ確認
下記コマンドでログの確認を行えます
-f
オプションを追加することで、継続的にログを確認できます。
その他にDocker Desktopの画面でログの確認等を行えます
docker-compose logs
コンテナ内のシェルへアクセスする
下記コマンドでコンテナ内部のシェルへアクセスすることができます。
nest-sample-project
の箇所がdocker-composeで指定したサービス名に対応します。
docker-compose exec nest-sample-project bash
root@c4393687a35c:/usr/src/app#
exit
コマンドでシェルから抜けられます
終了
下記コマンドで起動したコンテナを終了できます。
docker-compose down
[+] Running 2/2
- Container nest-sample-project-nest-sample-project-1 Removed 1.0s
- Network nest-sample-project_default Removed
キャッシュを利用せずにビルドを行う
下記コマンドで
たまにキャッシュが悪さをするときがあるので、そういったときはこのコマンドを利用してコンテナを再ビルドします。
docker-compose build --no-cache
参考: https://pointsandlines.jp/server-infra/docker/docker-compose-build-no-cache
本番環境のDockerfileの作成
主にsnyk社が自社の技術ブログで公開している「10 best practices to containerize Node.js web applications with Docker」とnodejsのリポジトリにある「Docker and Node.js Best Practices」を参考に、色々試行錯誤しながら作成しました。
# ビルド環境
FROM node:18.13.0-bullseye-slim as builder
WORKDIR /usr/src/app
## ビルド必要なパッケージをインストール
COPY package*.json ./
RUN npm install
## ビルドの実施
COPY . /usr/src/app
RUN npm run build
# 実行環境
FROM node:18.13.0-bullseye-slim
ENV NODE_ENV production
WORKDIR /usr/src/app
## ビルド環境からビルド済みのファイル等をコピーし、当該フォルダのオーナーをnodeユーザーへ変更
COPY --from=builder --chown=node:node /usr/src/app/ /usr/src/app/
## 動作に必要なパッケージのインストール
RUN npm ci --only=production
EXPOSE 3000
## nodeユーザーとして実行
USER node
CMD ["node", "dist/main"]
Dockerイメージのビルド及び動作確認
下記コマンドでDockerfileをビルドし、prod-nest-sample-project
というタグを付与します
docker build . -t prod-nest-sample-project
下記コマンドでprod-nest-sample-project
のタグがついたイメージを起動し、localhostの3000番ポートとコンテナの3000番ポートを接続します
docker run -p 3000:3000 -t prod-nest-sample-project
ブラウザでアクセスし、動作確認を行えます。
(追記)nestjsの終了時の処理
コンテナの終了時などにOSからSIGTERMが発行され、これを受けてnestjsでプログラムの終了処理を実行することができます。
https://docs.nestjs.com/fundamentals/lifecycle-events#application-shutdown
このあたりの機能はデフォルトでは無効(メモリリソースを使う処理のため)になっていますが、 main.ts
を下記のように変更することで有効にすることができます。
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Starts listening for shutdown hooks
app.enableShutdownHooks(); //追加
await app.listen(3000);
}
bootstrap();
で、上記機能を有効にすることで、きちんとSIGTERM
を取り扱ってくれるようです。(ドキュメント等の記載では特に見つけられていないです・・・)
dockerを停止する際に10秒後のSIGKILL
が発行されるまでコンテナが終了しなかったものが、上記処理を有効にすることで10秒待たずに終了するようになりました。
(追記) huskyによるpre-commitの利用
huskyを導入することで、git commit
実行直前に任意のコマンド(npm run format
とか)を実行することができます。
Automatic (recommended)にしたがって、huskyを導入します。
その後.husky/pre-commit
を編集することで任意のコマンドを実行することができます
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run format #追加
npm test