Validating NestJS env vars with zod

February 06, 2025 · Updated on March 21, 2025

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.

Setting Up ConfigModule

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 {}

Validating with Zod

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;
};

Extending ConfigService Types

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>>;

Using the Extended ConfigService

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 {}

tl;dr

  • Use NestJS's ConfigModule to load and validate env vars.
  • Register a validate function to ensure env vars are correct.
  • Use Zod to format and validate env vars.
  • Extend ConfigService types with Zod schema for type safety.
  • Implement the extended types in your modules for better type checking.