10

I'm trying to find the most legal way to set up NestJS database using .env file. That is I want to use @nestjs/config package for importing .env variables and use them in the TypeOrmModule.

It seems I need to use TypeOrmModule.forRootAsync.

I'm trying to do it like that:

// app.module.ts

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),
    TypeOrmModule.forRootAsync({
      useClass: TypeOrmConfigService,
    }),
    ...
  ],

})
export class AppModule {}

Then, there's TypeOrmConfigService:

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from '@nestjs/typeorm';

@Module({
  imports: [ConfigModule],
})
export class TypeOrmConfigService implements TypeOrmOptionsFactory {
  constructor(private configService: ConfigService) {}

  createTypeOrmOptions(): TypeOrmModuleOptions {
    return {
      type: 'mysql',
      host: this.configService.get('DATABASE_HOST'),
      username: this.configService.get('DATABASE_USERNAME'),
      password: this.configService.get('DATABASE_PASSWORD'),
    };
  }
}

The last one is incorrect: Nest can't resolve dependencies of the TypeOrmConfigService (?). Please make sure that the argument at index [0] is available in the TypeOrmCoreModule context.

How to fix it? Or (the most preferred) is there anywhere an example of NestJs + TypeOrm + @nestjs/config + .env (outside of the repo, with DATABASE_PASSWORD) + config (I mean npm package config that processes config/development.yml, config/production.yml etc.)?

It seems that I'm looking for a very standard thing, the hello world, that should be a start of every NestJS project, but I found difficulties combining @nestjs/config and TypeOrm.

Upd. If I replace @Module with @Injectable, the error is exactly the same:

yarn run v1.22.4
$ NODE_ENV=development nodemon
[nodemon] 1.19.0
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: /home/kasheftin/work/pubngn4/nestjs-course-task-management/src/**/*
[nodemon] starting `ts-node -r tsconfig-paths/register src/main.ts`
[Nest] 25384   - 09/01/2020, 8:07 PM   [NestFactory] Starting Nest application...
[Nest] 25384   - 09/01/2020, 8:07 PM   [InstanceLoader] AppModule dependencies initialized +11ms
[Nest] 25384   - 09/01/2020, 8:07 PM   [InstanceLoader] TypeOrmModule dependencies initialized +0ms
[Nest] 25384   - 09/01/2020, 8:07 PM   [InstanceLoader] PassportModule dependencies initialized +0ms
[Nest] 25384   - 09/01/2020, 8:07 PM   [ExceptionHandler] Nest can't resolve dependencies of the TypeOrmConfigService (?). Please make sure that the argument at index [0] is available in the TypeOrmCoreModule context. +1ms
Error: Nest can't resolve dependencies of the TypeOrmConfigService (?). Please make sure that the argument at index [0] is available in the TypeOrmCoreModule context.
    at Injector.lookupComponentInExports (/home/kasheftin/work/pubngn4/nestjs-course-task-management/node_modules/@nestjs/core/injector/injector.js:180:19)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
    at Injector.resolveComponentInstance (/home/kasheftin/work/pubngn4/nestjs-course-task-management/node_modules/@nestjs/core/injector/injector.js:143:33)
    at resolveParam (/home/kasheftin/work/pubngn4/nestjs-course-task-management/node_modules/@nestjs/core/injector/injector.js:96:38)
    at async Promise.all (index 0)
    at Injector.resolveConstructorParams (/home/kasheftin/work/pubngn4/nestjs-course-task-management/node_modules/@nestjs/core/injector/injector.js:112:27)
    at Injector.loadInstance (/home/kasheftin/work/pubngn4/nestjs-course-task-management/node_modules/@nestjs/core/injector/injector.js:78:9)
    at Injector.loadProvider (/home/kasheftin/work/pubngn4/nestjs-course-task-management/node_modules/@nestjs/core/injector/injector.js:35:9)
    at async Promise.all (index 3)
    at InstanceLoader.createInstancesOfProviders (/home/kasheftin/work/pubngn4/nestjs-course-task-management/node_modules/@nestjs/core/injector/instance-loader.js:41:9)
 1: 0xa2afd0 node::Abort() [/home/kasheftin/.nvm/versions/node/v14.3.0/bin/node]
 2: 0xa9e7a9  [/home/kasheftin/.nvm/versions/node/v14.3.0/bin/node]
 3: 0xc06bab  [/home/kasheftin/.nvm/versions/node/v14.3.0/bin/node]
 4: 0xc08156  [/home/kasheftin/.nvm/versions/node/v14.3.0/bin/node]
 5: 0xc087d6 v8::internal::Builtin_HandleApiCall(int, unsigned long*, v8::internal::Isolate*) [/home/kasheftin/.nvm/versions/node/v14.3.0/bin/node]
 6: 0x13a9f19  [/home/kasheftin/.nvm/versions/node/v14.3.0/bin/node]
Aborted (core dumped)
[nodemon] app crashed - waiting for file changes before starting...
Kasheftin
  • 7,509
  • 11
  • 41
  • 68

12 Answers12

12

It's my working script with Postgres in app.module.ts, but for MySQL will be very similar. And sometimes before rebuild need to remove dist folder manually - when DB can't synchronize.

import { ConfigModule, ConfigService } from '@nestjs/config';


@Module({
  imports: [
    ConfigModule.forRoot(),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],      
      useFactory: (configService: ConfigService) => ({
        type: 'postgres' as 'postgres',
        host: configService.get<string>('DATABASE_HOST'),
        port: parseInt(configService.get<string>('DATABASE_PORT')),
        username: configService.get<string>('DATABASE_USER'),
        password: configService.get<string>('DATABASE_PASS'),
        database: configService.get<string>('DATABASE_NAME'),
        entities: [__dirname + '/**/*.entity{.ts,.js}'],
        synchronize: true,
      }),
      inject: [ConfigService],
    }),

.env in root folder

DATABASE_USER=
DATABASE_PASS=
DATABASE_HOST=
DATABASE_NAME=
DATABASE_PORT=
V.Tur
  • 1,115
  • 9
  • 20
9

on database.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (configService: ConfigService) => ({
        type: 'mysql',
        host: configService.get('DATABASE_HOST'),
        port: configService.get('DATABASE_PORT'),
        username: configService.get('DATABASE_USERNAME'),
        password: configService.get('DATABASE_PASSWORD'),
        database: configService.get('DATABASE_NAME'),
        entities: ['dist/**/*.entity.js'],
        synchronize: true,
      }),
    }),
  ],
})
export class DatabaseModule {}

on app.module.ts

import { DatabaseModule } from './database/database.module';
import { ConfigModule } from '@nestjs/config';
import { Module } from '@nestjs/common';

@Module({
  imports: [
    ConfigModule.forRoot({}),
    DatabaseModule,
  ],
})
export class AppModule {}

This works for me

abbbel
  • 91
  • 2
  • 2
4

I had the same issue with this I just add the following:

import 'dotenv/config';

my app.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { typeOrmConfig } from '../ormconfig';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [ConfigModule.forRoot(), TypeOrmModule.forRoot(typeOrmConfig)],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

And my ormconfig looks like this:

import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import 'dotenv/config'; // <- this line is the important

const config: TypeOrmModuleOptions = {
  type: 'postgres',
  host: process.env.DB_HOST,
  port: parseInt(process.env.DB_PORT),
  username: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
};
export const typeOrmConfig = config;
Siumauricio
  • 101
  • 1
  • 5
2

Your TypeOrmConfigService should not be a @Module(). It should be @Injectable(). Everything else looks fine.

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from '@nestjs/typeorm';

@Injectable()
export class TypeOrmConfigService implements TypeOrmOptionsFactory {
  constructor(private configService: ConfigService) {}

  createTypeOrmOptions(): TypeOrmModuleOptions {
    return {
      type: 'mysql',
      host: this.configService.get('DATABASE_HOST'),
      username: this.configService.get('DATABASE_USERNAME'),
      password: this.configService.get('DATABASE_PASSWORD'),
    };
  }
}
Jay McDoniel
  • 57,339
  • 7
  • 135
  • 147
  • Sadly this does not work. The error is the same. It seems the config service is not passed to the constructor. That's why I tried to convert `@Injectable` to `@Module` because the last supports import prop, but still something is missing here. – Kasheftin Sep 01 '20 at 16:01
  • What's the error you get when you use `@Injectable()` rather than `@Module()`? This should be a `provider` so it should be `@Injectable()`. I've used this pattern before – Jay McDoniel Sep 01 '20 at 16:04
  • The error is exactly the same, Nest can't resolve dependencies of the TypeOrmConfigService (?). Please make sure that the argument at index [0] is available in the TypeOrmCoreModule context. Maybe isGlobal does not work as expected? – Kasheftin Sep 01 '20 at 17:08
  • Can you add the full error to the original question? – Jay McDoniel Sep 01 '20 at 17:10
  • Done. Done. Done. – Kasheftin Sep 01 '20 at 17:16
  • Out of curiosity, what version of Nest are you running? I haven't seen an error like that in a good while. – Jay McDoniel Sep 01 '20 at 17:21
2

you can try this:

// app.module.ts

@Module({
  imports: [
    ConfigModule.forRoot(),
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: process.env.DATABASE_HOST,
      username: process.env.DATABASE_USERNAME,
      password: process.env.DATABASE_PASSWORD,
    }),
    ...
  ],

})
export class AppModule {}
Hadas
  • 66
  • 2
2

Not sure if it is still relevant, however, I think this is whats going on.

The error that you see "Nest cant't resolve..." means that you are trying to inject a class using dependency injection, however Nest can't find this dependency in the dependencies container.

In order to resolve the issue, you need to add ConfigService (the failed to resolve dependency) to app.module.ts Providers section.

Something like that:

@Module({
   imports: [
        ConfigModule.forRoot(),
        TypeOrmModule.forRoot({
          type: 'mysql',
          host: process.env.DATABASE_HOST,
          username: process.env.DATABASE_USERNAME,
          password: process.env.DATABASE_PASSWORD,
        }),
        ...
   ],
   providers: [
        ConfigService
   ]
    
})
export class AppModule {}
yariv_kohn
  • 94
  • 4
2

Simple and Easy Way to do it

npm i @nestjs/config

 @Module({
  imports: [
    ConfigModule.forRoot(),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      name: process.env.DATABASE_NAME,
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => {
        return {
          type: 'mysql',
          host: configService.get(`DATABASE_HOST`),
          port: configService.get(`DATABASE_PORT`),
          username: configService.get('DB_USER'),
          database: configService.get('DATABASE_NAME'),
          password: configService.get('DB_PASSWORD'),
          entities: [__dirname + '/../entities/*.entity{.ts,.js}'],
          synchronize: false,
          logging: true,
        };
      },
    }),
  ],
1

Create a file (in this case the file is database.provider.ts) the file will export an array of connections.

import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
export const databaseProviders = [
  TypeOrmModule.forRootAsync({
    imports: [ConfigModule],
    inject: [ConfigService],
    useFactory: (configService: ConfigService) => ({
      type: 'postgres',
      host: configService.get('PGHOST'),
      port: +configService.get<number>('PGPORT'),
      username: configService.get('PGUSER'),
      password: configService.get('PGPASSWORD'),
      database: configService.get('PGDATABASE'),
      entities: ['dist/**/*.entity{.ts,.js}'],
      synchronize: false,
      logging: true,
    }),
  }),
];

Then import it in your database.module.ts

import { databaseProviders } from './database.provider';
import { DatabaseService } from './database.service';
import { Module } from '@nestjs/common';
@Module({
  imports: [...databaseProviders],
  providers: [DatabaseService],
  exports: [...databaseProviders],
})
export class DatabaseModule {}

And that's all, you can add multiple connections in your database.provider.ts if you want it, also, don´t forget to create the .env and import the database module in your root module.

Juan Rambal
  • 573
  • 2
  • 6
1

You can use "nestjs-easyconfig", i use it for multi .env (e.g: .env.development, .env.production' it's almost same with nestjs/config. you can check my github repo

Denny Danu
  • 61
  • 2
1

you can spit it to another module typeorm.module.ts

import { ConfigService } from '@nestjs/config';
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRootAsync({
      useFactory: async (config: ConfigService) => ({
        database: config.get<string>('DATABASE'),
        username: config.get<string>('USERNAME'),
        password: config.get<string>('PASSWORD'),
        host: config.get<string>('HOST'),
        port: parseInt(config.get('PORT')),
        autoLoadEntities: true,
        synchronize: true,
        type: 'postgres',
      }),
      inject: [ConfigService],
    }),
  ],
  controllers: [],
  providers: [],
})
export class TypeormConfigModule {}

main app.module.ts import like this*

import { TypeormConfigModule } from './config/database/typeorm.module';
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';


@Module({
  imports: [
    CaslModule,
    TypeormConfigModule,
    ConfigModule.forRoot({
      envFilePath: ['env/dev.env', 'env/prod.env', 'env/rom.env'],
      isGlobal: true,
    }),
  
  ],
})
export class AppModule {}


** env is folder name and prod.env is env file or you can put it directly at app.module.ts as above**

ANOTHER METHOD TO USE ENV TOGETHER IN APP.MODULES.TS DIRECTLY

@Module({
  imports: [
    CaslModule,
   TypeOrmModule.forRootAsync({
      useFactory: async (config: ConfigService) => ({
        database: config.get<string>('DATABASE'),
        username: config.get<string>('USERNAME'),
        password: config.get<string>('PASSWORD'),
        host: config.get<string>('HOST'),
        port: parseInt(config.get('PORT')),
        autoLoadEntities: true,
        synchronize: true,
        type: 'postgres',
      }),
      inject: [ConfigService],
    }),
]
}),
    ConfigModule.forRoot({
      envFilePath: ['env/dev.env', 'env/prod.env', 'env/rom.env'],
      isGlobal: true,
    }),
  
  ],
})
export class AppModule {}
BlueDragon
  • 57
  • 1
  • 7
  • The last method works for me. Thanks ! You can also use : type: config.get('TYPE') (in env file : TYPE=postgres) synchronize: config.get('SYNCHRONIZE') (in env file : SYNCHRONIZE=true) – bendeg Oct 20 '22 at 13:42
1

for me I just add this as my example

import { Injectable } from '@nestjs/common';
import { TypeOrmOptionsFactory, TypeOrmModuleOptions } from '@nestjs/typeorm';
import{Config} from'./config'
import { ConfigService } from '@nestjs/config';

@Injectable()
export class DatabaseConnectionService implements TypeOrmOptionsFactory {
  constructor(private configService: ConfigService) {}

  createTypeOrmOptions(): TypeOrmModuleOptions {
    return {
      name: 'default',
      type: 'postgres',
      host: this.configService.get<string>("DATABASE_HOST"),
      port: Number(this.configService.get<number>("DATABASE_PORT")) ,
      username: this.configService.get<string>("DATABASE_USERNAME"),
      password: this.configService.get<string>("DATABASE_PASSWORD"),
      database: this.configService.get<string>("DATABASE_DB"),
      synchronize: true,
      sslmode:"disable",
      logging: true,  
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
    
      migrations: [__dirname + "/migrations/**/*{.ts,.js}"],
      migrationsRun: false,
      maxQueryExecutionTime: 0.1 /** To log request runtime */,
      cli: {
        migrationsDir: __dirname + "/migrations/**/*{.ts,.js}",
      },
      
      
    }
  }
}

in the app.module.ts just add this code in import section

imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),

    TypeOrmModule.forRootAsync({
      useClass: DatabaseConnectionService,
    })]
Mohammed Al-Reai
  • 2,344
  • 14
  • 18
0

You should only import ConfigModule to TypeOrmCoreModule:

TypeOrmModule.forRootAsync({
  imports: [ConfigModule], // import ConfigModule
  useClass: TypeOrmConfigService,
}),
ta_shuo
  • 101
  • 5