1

I'm trying to set up all of my configurations in one file in "config.ts", load it to ConfigService and then with config interfaces get values from it. So here is my config.ts that have ENV vars from my .env file and static variables.

UPD: Made repo with this example

import { Config } from './config.interface';

const config: Config = {
  typeorm: {
    type: 'postgres',
    host: process.env.DB_HOST,
    port: +process.env.DB_PORT,
    username: process.env.DB_USERNAME,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME,
    synchronize: process.env.NODE_ENV !== 'prod',
    logging: true,
    entities: [User, RefreshToken],
  },
};

export default () => config;

And here is my interfaces:

export interface Config {
  typeorm: TypeOrmConfig;
}

export interface TypeOrmConfig {
  type: string;
  host: string;
  port: number;
  username: string;
  password: string;
  database: string;
  synchronize: boolean;
  logging: boolean;
  entities: any[];
}

The config is loaded to ConfigModule in app.module.ts

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: '.dev.env',
      load: [config],
    }),
}),

For example I want to set up my TypeOrmModule with this setting. Based on NestJs documentation

TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => {
        const config = configService.get<TypeOrmConfig>('typeorm');
        console.log(config);
        return {
          type: config.type,
          host: config.host,
          port: +config.port,
          username: config.username,
          password: config.password,
          database: config.database,
          synchronize: config.synchronize,
          logging: config.logging,
          entities: config.entities,
        };
      },
      inject: [ConfigService],
    }),

Here is the problem. Static values from config is okay, but all my ENV vars is undefined. Here is console.log output:

{
  type: 'postgres',
  host: undefined,
  port: NaN,
  username: undefined,
  password: undefined,
  database: undefined,
  synchronize: true,
  logging: true,
  entities: [
    [class User extends CoreEntity],
    [class RefreshToken extends CoreEntity]
  ]
}

I don't understand what's the problem with undefined ENV vars I would be grateful for any explanations and help

Denis Penkov
  • 23
  • 1
  • 4

1 Answers1

8

In short, NestJS requires you to define a namespace for your custom configuration "typeorm" which you are trying to access here (see Configuration Namespace):

const config = configService.get<TypeOrmConfig>('typeorm');

This means that you must use registerAs function to create your namespace:

import { registerAs } from '@nestjs/config';
import { TypeOrmConfig } from './config.interface';

export default registerAs(
  'typeorm',
  (): TypeOrmConfig => ({
    type: 'postgres',
    host: process.env.DB_HOST,
    port: +process.env.DB_PORT,
    username: process.env.DB_USERNAME,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME,
    synchronize: process.env.NODE_ENV !== 'prod',
    logging: true,
    entities: [],
  }),
);

This will do the trick. However, we can improve upon this.

Instead of defining your own interface for the TypeORM configuration, there are existing interfaces you can use and / or extend from to reduce boilerplate code (below I mention ConnectionOptions though TypeOrmModuleOptions might be more appropriate depending on your configuration needs). I would also suggest providing fallback values to environmental variables:

import { registerAs } from '@nestjs/config';
import { ConnectionOptions } from 'typeorm';

const CONNECTION_TYPE = 'postgres';

export default registerAs(
  'typeorm',
  (): ConnectionOptions => ({
    type: CONNECTION_TYPE,
    host: process.env.DB_HOST || 'default_value',
    port: +process.env.DB_PORT || 3000,
    username: process.env.DB_USERNAME || 'default_value',
    password: process.env.DB_PASSWORD || 'default_value',
    database: process.env.DB_NAME || 'default_value',
    synchronize: process.env.NODE_ENV !== 'prod',
    logging: true,
    entities: [],
  }),
);  

Also, there is no need to explicitly assign configuration values to TypeOrmModule again as you can simply:

TypeOrmModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: async (configService: ConfigService) =>
    await configService.get('typeorm'),
  inject: [ConfigService],
}),

As a personal flavour I like to create configuration enum to contain configuration names to prevent misspelling. Consider this:

export enum ConfigEnum {
  TYPEORM = 'typeorm',
}

TypeOrmModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: async (configService: ConfigService) =>
    await configService.get<Promise<TypeOrmModuleOptions>>(
      ConfigEnum.TYPEORM,
    ),
  inject: [ConfigService],
})

Async / Await pattern is also redundant here, as we do not actually perform anything async, so this will work as well:

TypeOrmModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: (configService: ConfigService) =>
    configService.get<TypeOrmModuleOptions>(ConfigEnum.TYPEORM),
  inject: [ConfigService],
})

Welcome to StackOverflow!

Ps. I have made a pull request, see https://github.com/JustDenP/nest-config/pull/1

Matvei Kinner
  • 366
  • 1
  • 7
  • Wow, thank you very much! This is exactly what was needed! – Denis Penkov Apr 21 '21 at 07:40
  • But actually there is one more thing that I don't understand. As I can see, there is no way to make namespaced config nested from single config.ts file which should contain multiple configurations? – Denis Penkov Apr 21 '21 at 08:09
  • @DenisPenkov - If you want to have all configs in the same file simply change from default export to named export. See for example this answer: https://stackoverflow.com/questions/46913851/why-and-when-to-use-default-export-over-named-exports-in-es6-modules. In short, instead of `export default` use `export const` and name your export. Though I would suggest separating different configs into their own files as this is Imo better separation of concerns though they are all configs at the end of the day. – Matvei Kinner Apr 21 '21 at 11:51
  • Perfect! Exactly what I was looking for. In fact, better than I expected =) – vdiaz1130 Mar 24 '23 at 01:13