Nest(TypeScript)で遊んでみる 〜Configuration編〜
Posted on September 18, 2018 at 01:30 (JST)
今回は環境別設定を .env
ファイルに定義し、Nestアプリケーションで利用する方法について記載する。
- ライブラリをインストール
- 環境別設定を読み込む
- TypeORMの設定として利用する
作成したコード [ nest-angular-sample: works/08_configuration ]
動作環境
OS: macOS High Sierra ver. 10.13.4
Nodejs: v8.10.0
npm: 5.6.0
nest(core): 5.3.6
Docker: 18.06.1-ce
1. ライブラリをインストール
公式ドキュメント を参考に進める。
今回はライブラリを2つインストールする。
$ npm install --save dotenv joi
$ npm install --save-dev @types/dotenv @types/joi
dotenv
.env
ファイルに記載した設定をobjectへと変換するのに使用する。
dotenv (Github)
joi
JavaScriptのobjectの検証に利用する。
joi (Github)
2. 環境別設定を読み込む
2-1. .envファイルを配備する
下記3点ファイルを用意する
- env/dev.env
- env/prod.env
- env/test.env
内容は環境にあわせ適宜変更する
DATABASE_HOST=localhost
DATABASE_PORT=3306
DATABASE_USER=user
DATABASE_PASSWORD=password
TYPEORM_SYNCHRONIZE=false
2-2. ConfigServiceを用意する
[src/config/config.service.ts]
import * as dotenv from 'dotenv';
import * as Joi from 'joi';
import * as fs from 'fs';
export interface EnvConfig {
[key: string]: string;
}
export interface OrmEnv {
host: string;
port: number;
user: string;
password: string;
synchronize: boolean;
}
export class ConfigService {
private readonly envConfig: EnvConfig;
constructor(filePath: string) {
const config = dotenv.parse(fs.readFileSync(filePath));
const merged = Object.assign({}, config, process.env);
this.envConfig = this.validateInput(merged);
}
private validateInput(envConfig: EnvConfig): EnvConfig {
const envVarsSchema: Joi.ObjectSchema = Joi.object({
ENV_NAME: Joi.string()
.valid(['dev', 'prod', 'test'])
.default('dev'),
DATABASE_HOST: Joi.string().required(),
DATABASE_PORT: Joi.number().required(),
DATABASE_USER: Joi.string().required(),
DATABASE_PASSWORD: Joi.string().required(),
TYPEORM_SYNCHRONIZE: Joi.boolean(),
});
const { error, value: validatedEnvConfig } = Joi.validate(
envConfig,
envVarsSchema,
{ stripUnknown: true },
);
if (error) {
throw new Error(`Config validation error: ${error.message}`);
}
return validatedEnvConfig;
}
get typeOrm(): OrmEnv {
return {
host: this.envConfig.DATABASE_HOST,
user: this.envConfig.DATABASE_USER,
port: parseInt(this.envConfig.DATABASE_PORT, 10),
password: this.envConfig.DATABASE_PASSWORD,
synchronize: Boolean(this.envConfig.TYPEORM_SYNCHRONIZE),
};
}
}
ポイントは下記になる
dotenv.parse
でファイルの内容をobject化- コマンドライン引数や
.bash_profile
にて環境変数の指定があった場合は値を上書きする。(本番パスワード等を.envファイルに書かなくてすむ) Joi.ObjectSchema
に沿った入力値となっているか検証できる。Joi.validate
にオプション(stripUnknown: true)を設定することでスキーマに定義した値以外を削ぎ落とすことができる。- dotenvで読み込んだ値やprocess.envの値はstringなので適宜Castする必要がある。
2-3. ConfigModuleを用意する
[src/config/config.module.ts]
import { Module } from '@nestjs/common';
import { ConfigService } from './config.service';
@Module({
providers: [
{
provide: ConfigService,
useValue: new ConfigService(`env/${process.env.ENV_NAME}.env`),
},
],
exports: [ConfigService],
})
export class ConfigModule {}
公式ドキュメントでは NODE_ENV
という値で読み込むファイルを変えている。
今回別の環境変数を用意しているのは、フレームワークで想定しているパターン以上に環境が増えることを想定しているからである。
NODE_ENV
はフレームワーク内で制御用に使用されていることが多い。
下手に独自環境名を追加すると予期せぬエラーにつながるため、開発者が制御に用いるための別の変数を用意している。
3. TypeORMの設定として利用する
3-1. TypeORM用の設定Providorを用意する
[src/config/typeorm.config.ts]
import { TypeOrmOptionsFactory, TypeOrmModuleOptions } from '@nestjs/typeorm';
import { TaskEntity } from '../tasks/entities/task.entity';
import { Injectable } from '@nestjs/common';
import { ConfigService } from './config.service';
@Injectable()
export class TypeOrmConfig implements TypeOrmOptionsFactory {
constructor(private readonly config: ConfigService) {}
createTypeOrmOptions(): TypeOrmModuleOptions {
return {
type: 'mysql',
host: this.config.typeOrm.host,
port: 3306,
username: this.config.typeOrm.user,
password: this.config.typeOrm.password,
database: 'test_db',
entities: [TaskEntity],
synchronize: this.config.typeOrm.synchronize,
};
}
}
※ 前回用意したdb.config.tsは不要となるので削除。
3-2. ConfigModuleに追加
import { Module } from '@nestjs/common';
import { ConfigService } from './config.service';
import { TypeOrmConfig } from './typeorm.config';
@Module({
providers: [
{
provide: ConfigService,
useValue: new ConfigService(`env/${process.env.ENV_NAME}.env`),
},
TypeOrmConfig,
],
exports: [ConfigService, TypeOrmConfig],
})
export class ConfigModule {}
3-3. AppModule変更
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TasksModule } from './tasks/tasks.module';
import { LoggerModule } from './logger/logger.module';
import { SamplesModule } from './samples/samples.module';
import { TypeOrmConfig } from './config/typeorm.config';
import { ConfigModule } from './config/config.module';
@Module({
imports: [
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useExisting: TypeOrmConfig,
}),
LoggerModule,
TasksModule,
SamplesModule,
],
controllers: [],
providers: [],
})
export class AppModule {}
TypeOrmModule.forRootAsync
を使って設定を読み込むのがポイント。
AppModule初期化時にはまだ、TypeOrmConfigが初期化されていないため非同期で読み込む必要がある。
以上。