Zod is the obvious choice for parsing request bodies, and environment variables deserve the same treatment. The difference is timing. You want env vars parsed once during boot, then used with confidence everywhere else. NestJS gives you a place to do that: the ConfigModule from @nestjs/config.
ConfigModule is a global module that reads your env file and loads it into the process. It also takes a validate function, which runs before the rest of the app boots. That hook is where Zod env validation goes:
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. So the schema is a plain z.object and the function is a one-liner. NestJS Zod validation for request bodies normally runs through a pipe or the nestjs-zod package; env vars skip both, because they only need to be parsed once:
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;
};If DATABASE_URL is missing, parse throws and the app never starts.
You can also extend the ConfigService types with your Zod schema. That gives you type safety everywhere the ConfigModule is used, with no second source of truth. Here's how you define the type:
import type { ConfigService } from "@nestjs/config";
export type IConfigService = ConfigService<z.infer<typeof envs>>;With the typed ConfigService in place, getOrThrow from @nestjs/config returns the schema type for a key instead of string | undefined, so no cast is needed at the call site. Use it 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.Occasional notes on software, tools, and things I learn. No spam.
Unsubscribe anytime.