Hey there! If you're like me, you probably love using Zod for validating and parsing data objects. It's a lifesaver, right? But when it comes to environment variables (env vars), things can get a bit tricky. You want to parse them as quickly as possible during your application's boot-up phase and then use them confidently throughout your app. That's where NestJS's ConfigModule comes into play.
NestJS provides a handy ConfigModule that lets you register a global module to read your env file and load it into the process. But here's the catch: you need to validate those env vars to ensure they're correct. Luckily, the ConfigModule allows you to register a validate
function. Here's a quick look at how you can set it up:
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true, validate: /* Your function */ }),
]
})
export class AppModule {}
The validate
function receives the full process.env
object and expects a validated, parsed object in return. This is where Zod comes in handy. You can use it to format and validate your env vars like this:
import { z } from 'zod';
const envs = z.object({
DATABASE_URL: z.string(),
NODE_ENV: z.string().optional(),
});
export const validate = (config: Record<string, unknown>) => {
const validated = envs.parse(config);
return validated;
};
As a bonus, you can extend the ConfigService types with your Zod schema. This ensures that whenever you're using the ConfigModule, you have type safety. Here's how you can define your type:
import type { ConfigService } from '@nestjs/config';
export type IConfigService = ConfigService<z.infer<typeof envs>>;
Once you've set up your types, you can use them in your modules like so:
@Module({
imports: [
DrizzlePostgresModule.registerAsync({
tag: DB_TAG,
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: IConfigService) => ({
postgres: {
url: configService.getOrThrow('DATABASE_URL'),
}
}),
}),
],
})
export class DbModule {}
validate
function to ensure env vars are correct.