Creating a configuration module like a specialist with Zod inside NestJS

Henrique Weiand
NestJS Ninja
Published in
4 min readOct 10, 2023

Hello fellow coders! Today we are going to talk about something simple that basically everybody and every project does, which is using ENVs inside a project, it can be database credentials, project port, name of queue, or whatever else.

Doing this configuration can be also easy to do, you just need to use the ConfigModule right? Yeah, it is basically like that, and just with a few lines of code it is already working, however, today I want to share some cool tips to enhance the quality of this module that you are importing to your project the points that we are going to check out are:

  • Creating a module to have this ENV ConfigureModule with as little accomplishment as possible
  • Configure it to use as many as features Typescript can offer us.

Creating the project

Let’s start by creating a new project that I am going to call nestjs-config-module

nest new nestjs-config-module

Creating a module

In order to have a structured and segmented project I strongly recommend creating a new module to be in charge of dealing with the envs for you, so when necessary you will only need to maintain one central place when talking about environments variables. Let’s do it

nest g module env

We also will need a service, so let’s use the command line again

nest g service env

Well done! We are going to use two dependencies, so before we continue the project let’s install them as well.

yarn add zod @nestjs/config

As we are planning to use this module as a base for the other import and use the methods, we have to turn this module importable, that is the reason that we are exporting our service here

import { Module } from '@nestjs/common';
import { EnvService } from './env.service';
@Module({
providers: [EnvService],
exports: [EnvService],
})
export class EnvModule { }

Before editing our service, we will need to create a file called env.ts, with this content

import { z } from 'zod';
export const envSchema = z.object({
PORT: z.coerce.number().optional().default(3000),
});
export type Env = z.infer<typeof envSchema>;

This is one important file to have in mind because it will be responsible for the ENVs inside of your project and using zod you can create the whole type of each one of the vars and also define a default when necessary (Very useful).

To learn more about ZOD, please check out this link.

The method z.infer in this example is what creates the magic of having a type automatically.

Next, let’s edit our env.service.ts and here we just need to something like this

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Env } from './env';
@Injectable()
export class EnvService {
constructor(private configService: ConfigService<Env, true>) { }
get<T extends keyof Env>(key: T) {
return this.configService.get(key, { infer: true });
}
}

IMPORTANT: we are using ConfigService inside this service and we are not importing it in our EnvModule indeed, I know it. I am doing that because we are going to check the app.module.tswhere we are going to add the global module.

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { EnvModule } from './env/env.module';
import { envSchema } from './env/env';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
validate: (env) => envSchema.parse(env),
isGlobal: true,
}),
EnvModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule { }

Here we are importing the ConfigModule and setting it as a global module in the highest module that this project has, so our EnvModule can use it and other parts of the code as well, which is useful for some things for tests or whatever else. The validate index is setting our schema from env.ts

that shares the type that will be validated when the application starts.

At the root of your project, you have to create a file called .env where you need to put the envs that are going to be loaded when the app starts.

And that’s all!

Why is it so useful?

With this approach as I mentioned before, we are centralizing inside one module how to handle vars, we are validating if the project has all necessary variables to run, and we are also using resources from the editor to help us to check which variables are available.

Example project

--

--

Henrique Weiand
NestJS Ninja

Software developer at trilon.io, the main company behind the NestJS framework.