【NestJS】ConfigServiceをカスタムして.env(環境変数)をバリデーションする
困ってること
@nestjs/config
を利用していると下記のように少し困ることがある。
- 毎回
configService.get<string>('xxx')
という風にする場合、厳格な型安全性が無く変数名の補完も効かない - 想定した環境変数とその値が記述されているかの検証&保証ができてない
- 環境毎に
.env
ファイルを作成している場合、一部のファイルでのみ環境変数を設定し忘れるリスクもある
- 環境毎に
そこでConfigModule
をラップしてより便利にしつつ、.env(環境変数)のバリデーションもしてみる。
実装
0. 環境変数の設定
.env.developmentDATABASE_HOST=xxx
DATABASE_NAME=xxx
DATABASE_USER=xxx
DATABASE_PASSWORD=xxx
DATABASE_PORT=xxx
今回は上記のような環境変数を設定している前提で進める。
1. EnvModuleの実装
nest g module env
nest g service env
env.module.tsimport { Global, Module } from '@nestjs/common';
import { EnvService } from './env.service';
import { ConfigModule } from '@nestjs/config';
import { validate } from './env-validator';
@Global()
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: `.env.${process.env.NODE_ENV}`,
}),
],
providers: [EnvService],
exports: [EnvService],
})
export class EnvModule {}
グローバルに使いたいので@Global
デコレータも付与しておく。
2. EnvServiceの実装
env.service.tsimport { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
type DbConfig = {
host?: string;
name?: string;
user?: string;
password?: string;
port?: number;
};
@Injectable()
export class EnvService {
constructor(private readonly configService: ConfigService) {}
isDev(): boolean {
return this.configService.get<string>('NODE_ENV') === 'development';
}
get dbConfig(): DbConfig {
return {
host: this.configService.get<string>('DATABASE_HOST'),
name: this.configService.get<string>('DATABASE_NAME'),
user: this.configService.get<string>('DATABASE_USER'),
password: this.configService.get<string>('DATABASE_PASSWORD'),
port: this.configService.get<number>('DATABASE_PORT'),
};
}
}
ここでConfigService
をDIし、設定した環境変数のgetterを追加する。
import { EnvService } from '../env/env.service';
@Injectable()
export class SampleService {
constructor(
private readonly envService: EnvService,
) {}
sample() {
const { host, name, user, password, port } = this.envService.dbConfig();
}
}
あとは利用元でEnvService
をDIすればよし。これでより型安全に環境変数を取り出せるようになった。
3. EnvValidatorの実装
touch env/env-validator.ts
npm i zod
環境変数をバリデーションするためのクラスを作成し、必要に応じてライブラリ(今回はzod
)をインストールする。
env-validator.tsimport { z } from 'zod';
const NODE_ENVS = ['development', 'test', 'production'];
const zodString = z.string().min(1);
const envSchema = z.object({
NODE_ENV: z.string().refine((v) => NODE_ENVS.includes(v)),
DATABASE_HOST: zodString,
DATABASE_NAME: zodString,
DATABASE_USER: zodString,
DATABASE_PASSWORD: zodString,
DATABASE_PORT: z
.string()
.regex(/^\d+$/, { message: '数値の文字列を入力してください' }),
});
export function validate(
config: Record<string, unknown>,
): Record<string, unknown> {
envSchema.parse(config);
return config;
}
ルールを記述の上validate
関数を作成し、その中で判定を行う。
env.module.tsimport { Global, Module } from '@nestjs/common';
import { EnvService } from './env.service';
import { ConfigModule } from '@nestjs/config';
import { validate } from './env-validator';
@Global()
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: `.env.${process.env.NODE_ENV}`,
validate,
}),
],
providers: [EnvService],
exports: [EnvService],
})
export class EnvModule {}
あとはConfigModule
のオプションに先ほどのvalidate
関数を指定すれば完了。
4. 動作確認
.env.developmentDATABASE_HOST=xxx
試しに適当な環境変数を削除してみる。
const error = new ZodError_1.ZodError(ctx.common.issues); ^ ZodError: [ { "code": "invalid_type", "expected": "string", "received": "undefined", "path": [ "DATABASE_HOST" ], "message": "Required" } ]
すると無事にバリデーションエラーが表示された。