2023.2.27
NestJSで開発環境と本番環境とテストでそれぞれに対応したenvファイルを読み込む方法
概要
NestJSでは カスタムenvファイルパスを定義する方法 が提供されていますが、機能的には複数のenvファイルを読み込んで同じ環境変数が複数のenvファイルで定義されていれば最初に読み込んだものに定義された値を優先するというものなので、この記事ではcross-envと@nestjs/configを使って開発環境と本番環境とテストでそれぞれに対応したenvファイルを読み込む方法を公開します。
今回の内容は サンプルリポジトリ に公開しています。
手順
事前準備
もし @nestjs/cli
がグローバルにインストールされていなかったらインストールします。
bash_____terminal_____$ npm i -g @nestjs/cli
NestJSアプリケーションを作成
任意のディレクトリにNestJSアプリケーションを作成します。
bash_____terminal_____$ nest new .
今回はyarnを使用して進めていきます。
envファイルを作成
text_____src/envs/development.env_____ENV_FILE_NAME=development.env
text_____src/envs/production.env_____ENV_FILE_NAME=production.env
text_____src/envs/test.env_____ENV_FILE_NAME=test.env
nest-cli.jsonを更新
JSON_____nest-cli.json_____{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true,
"assets": ["envs/*"]
}
}
NODE_ENVに対応したenvファイルパスを返す関数
TypeScript_____src/utils/getEnvPath.ts_____import { existsSync } from 'fs';
import { resolve } from 'path';
export const getEnvFilePath = (envFilesDirectory: string) => {
const env = process.env.NODE_ENV;
const fileName = env ? `${env}.env` : 'development.env';
const filePath = resolve(`${envFilesDirectory}/${fileName}`);
return existsSync(filePath) ? filePath : resolve(`${envFilesDirectory}/.env`);
};
関連ファイルを更新
bash_____terminal_____$ yarn add @nestjs/config
TypeScript_____src/app.module.ts_____import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { getEnvFilePath } from './utils/getEnvFilePath';
const envFilePath: string = getEnvFilePath(`${__dirname}/envs`);
@Module({
imports: [ConfigModule.forRoot({ envFilePath, isGlobal: true })],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
TypeScript_____src/app.service.ts_____import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getEnvFileName(): string {
return `ENV_FILE_NAME: ${process.env.ENV_FILE_NAME ?? 'Undefined'}`;
}
}
TypeScript_____src/app.controller.ts_____import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getEnvFileName(): string {
return this.appService.getEnvFileName();
}
}
TypeScript_____src/app.controller.spec.ts_____import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { ConfigModule } from '@nestjs/config';
import { AppService } from './app.service';
import { getEnvFilePath } from './utils/getEnvFilePath';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const envFilePath: string = getEnvFilePath(`${__dirname}/envs`);
const app: TestingModule = await Test.createTestingModule({
imports: [ConfigModule.forRoot({ envFilePath, isGlobal: true })],
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "ENV_FILE_NAME: test.env"', () => {
expect(appController.getEnvFileName()).toBe('ENV_FILE_NAME: test.env');
});
});
});
cross-envを追加してscriptsを更新
bash_____terminal_____$ yarn add -D cross-env
JSON_____package.json_____{
// ...,
"scripts": {
"start:dev": "cross-env NODE_ENV=development nest start --watch",
"start:prod": "cross-env NODE_ENV=production node dist/main",
"test": "cross-env NODE_ENV=test jest",
// ...,
},
// ...,
}
読み込まれているenvファイルを確認
開発環境
開発環境にするため yarn start:dev
を実行したあとcurlで環境変数を確認してみます。
text_____terminal_____curl http://localhost:3000/
ENV_FILE_NAME: development.env%
本番環境
本番環境にするため yarn start:prod
を実行したあとcurlで環境変数を確認してみます。
text_____terminal_____curl http://localhost:3000/
ENV_FILE_NAME: production.env%
テスト
最後にテストを実行してみます。
text_____terminal_____$ yarn test
yarn run v1.22.19
$ cross-env NODE_ENV=test jest
PASS src/app.controller.spec.ts
AppController
root
✓ should return "ENV_FILE_NAME: test.env" (16 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.353 s, estimated 3 s
Ran all test suites.
✨ Done in 2.96s.
まとめ
上記の方法で開発環境、本番環境、テストでそれぞれに対応したenvファイルを読み込めたことが確認できました。
NestJSが提供している機能よりもう少しソリッドなアプローチとなるので、お好みで使ってみてください。