# Easy Steps to Generate a Config Module in NestJS

When I started with NestJS, I didn't understand modules, interceptors, guards, and other advanced techniques the framework offers. As a result, I built insecure applications with poor practices. Over time, I learned the hard way which patterns truly matter for production-grade applications.

After several years working with NestJS, I've collected capabilities I want to share. One is creating a custom config module where you can declare, load, and validate environment variables. After reading this post, you'll build more robust, production-ready applications.

## Setting up the project

First, I'll install the Nest CLI globally to access the framework's capabilities. I like using the CLI to boost my productivity when working with Nest.

```bash
npm install -g @nestjs/cli
```

With the CLI installed, I can create a new Nest project using the command `nest new`. I prefer working with pnpm as my package manager, but you can choose whichever works best for you. I'll name my project `config-module-example`.

```bash
nest new config-module-example
```

Now I'll install the config service from Nest.

```bash
pnpm i @nestjs/config
```

With the project set up, it's time to start building. I'll begin by creating a new module.

## Creating the Module

As a best practice, I create a common folder for configurations, database schemas, definitions, validations, and other non-business logic utilities. Let's start there.

Using the CLI, I'll run this command:

```bash
nest g mo common/config
```

This command creates a config module inside the common folder and a `config.module.ts` file where I'll define my custom configuration module—don't confuse it with the built-in one.

To make my module global, I'll add the `@Global` decorator so I can use it throughout the entire application. I also need to import the `@nestjs/config` package but rename it as `NestConfigModule` to avoid confusion with my own config module.

```typescript
import { ConfigModule as NestConfigModule } from '@nestjs/config';
```

Now I'll configure how the module behaves. Since I'm working with .env files, I need to tell the Nest container where to find them and enable caching. I also need to export this module so it can be used as a provider.

With these configurations in place, my module looks like this:

```typescript
@Global()
@Module({
  imports: [
    NestConfigModule.forRoot({
      isGlobal: true,
      envFilePath: [
        `.env${process.env.NODE_ENV ? `.${process.env.NODE_ENV}` : ''}`,
      ],
      cache: true,
    }),
  ],
  exports: [NestConfigModule],
})
export class ConfigModule {}
```

Pay attention to the `envFilePath` property—this tells the module to use the environment-specific .env file if `NODE_ENV` is present, or the default one if not. With my config module defined, I'll now define the types for working.

## Defining Configuration Types

For this example, I'll declare some basic environment variables. You can extend them as needed.

Under the common folder, I'll create a types folder with a file called `app.types.ts`. Here I'll define the `AppConfig` type:

```typescript
export type AppConfig = {
  port: number;
  nodeEnv: 'development' | 'production' | 'test' | 'staging';
}; 
```

With the types defined, I can now set up the application's configuration.

## Working with Configurations

With the types defined, I'll now create the configuration file. Inside the `config` folder, I'll create a `configurations` folder and add a file called `app.config.ts`. I need to import the `registerAs` function from `@nestjs/config`. This function registers a configuration namespace that I can reference later. Here's how it looks:

```typescript
export default registerAs(
  'app',
  (): AppConfig => ({
    port: parseInt(process.env.PORT || '3000', 10),
    nodeEnv: (process.env.NODE_ENV || 'development') as
      | 'development'
      | 'production'
      | 'test'
      | 'staging',
  }),
);
```

I'm using `registerAs` with two arguments: the namespace token `'app'` and a factory function that returns the `AppConfig` object. This maps my environment variables to a type-safe configuration. But what happens if someone loads a corrupt .env file with invalid variables? To prevent this, the next step is to define and validate a schema.

## Validating Schemas with Joi

With the configuration in place, I need to ensure the .env file doesn't have corrupt or invalid variables. To do this, I'll create a schema with validations using Joi.

First, I'll install Joi:

```bash
pnpm i joi
```

Next, I'll create a validations folder under my config module to hold my schemas. Using Joi, I'll define the validation schema:

```typescript
export const appValidationSchema = Joi.object({
  PORT: Joi.number().default(3000),
  NODE_ENV: Joi.string()
    .valid('development', 'production', 'test')
    .default('development'),
});
```

The schema validates that `PORT` is a number and `NODE_ENV` is one of the allowed environment values, with sensible defaults for both. With all the pieces ready, it's time to wire everything together and make this configuration work.

## Wiring It All Together

Now I'll connect all the pieces. I'll return to the `config.module.ts` file and add the configuration loader and validation schema:

```typescript
@Global()
@Module({
  imports: [
    NestConfigModule.forRoot({
      isGlobal: true,
      load: [appConfig],
      validationSchema: appValidationSchema,
      envFilePath: [
        `.env${process.env.NODE_ENV ? `.${process.env.NODE_ENV}` : ''}`,
      ],
      cache: true,
    }),
  ],
  exports: [NestConfigModule],
})
export class ConfigModule {}
```

The `load` property tells the module which configuration files to load, and `validationSchema` ensures the environment variables are valid before the app starts. With the module complete, let's test that it works.

## Testing the Configuration works

To test the configuration, I'll log the port and environment at startup. In my `main.ts` file, I'll inject the `ConfigService` and retrieve the configuration values:

```typescript
const configService = app.get(ConfigService);

const port = configService.get<number>('app.port', { infer: true }) as number;

const nodeEnv = configService.get<string>('app.nodeEnv', { infer: true }) as string;

if (nodeEnv === 'development') {

	console.log(`Application is running in ${nodeEnv} mode`);

	console.log(`Listening on port ${port}`);

}
```

Before running the application, I'll create a `.env` file with the environment variables:

```plaintext
PORT=3000

NODE_ENV=development
```

Now I'll run `pnpm run start:dev`. If everything is configured correctly, the application will start and display the development logs.

## Final Thoughts

Creating a custom configuration module in NestJS is essential for building robust, maintainable applications. In this article, I've shown you how to structure environment variables, validate them with Joi schemas, and organize them in a way that scales with your project.

This setup prevents the configuration mistakes I made early in my NestJS journey. Type-safe configurations with validation catch errors before they reach production, making your applications more reliable and easier to maintain.

You can find the complete code in my Github repository: [https://github.com/RubenOAlvarado/config-module-example](https://github.com/RubenOAlvarado/config-module-example)

Happy coding—see you in the next one!
