【NestJS】ConfigServiceをカスタムして.env(環境変数)をバリデーションする
困ってること
@nestjs/config
を利用していると下記のように少し困ることがある。
- 毎回
configService.get<string>('xxx')
という風にする場合、厳格な型安全性が無く変数名の補完も効かない - 想定した環境変数とその値が記述されているかの検証&保証ができてない
- 環境毎に
.env
ファイルを作成している場合、一部のファイルでのみ環境変数を設定し忘れるリスクもある
- 環境毎に
そこでConfigModule
をラップしてより便利にしつつ、.env(環境変数)のバリデーションもしてみる。
実装
0. 環境変数の設定
DATABASE_HOST=xxxDATABASE_NAME=xxxDATABASE_USER=xxxDATABASE_PASSWORD=xxxDATABASE_PORT=xxx
今回は上記のような環境変数を設定している前提で進める。
1. EnvModuleの実装
nest g module envnest g service env
import { 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の実装
import { 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.tsnpm i zod
環境変数をバリデーションするためのクラスを作成し、必要に応じてライブラリ(今回はzod
)をインストールする。
import { 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
関数を作成し、その中で判定を行う。
import { 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. 動作確認
DATABASE_HOST=xxx
試しに適当な環境変数を削除してみる。
const error = new ZodError_1.ZodError(ctx.common.issues); ^ZodError: [ { "code": "invalid_type", "expected": "string", "received": "undefined", "path": [ "DATABASE_HOST" ], "message": "Required" }]
すると無事にバリデーションエラーが表示された。
参考