Configuring a NestJS application to establish a TypeOrm connection using environment variables and @nestjs/config

Looking for the best way to set up a NestJS database using a .env file in compliance with legal requirements. The goal is to utilize the @nestjs/config package to import .env variables and incorporate them into the TypeOrmModule.

It appears that utilizing TypeOrmModule.forRootAsync is necessary.

The attempt at implementation looks like this:

// app.module.ts

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

})
export class AppModule {}

Next, there's the 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 error message indicates an issue:

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

How can this be resolved? Alternatively, is there an example available that demonstrates the integration of NestJs + TypeOrm + @nestjs/config + .env (storing DATABASE_PASSWORD outside of the repository) + configuration (utilizing npm package config to process config/development.yml, config/production.yml, etc.)?

This seems like a very common requirement, essentially the "hello world" setup for every NestJS project, but combining @nestjs/config and TypeOrm presents challenges.

Update: Switching from @Module to @Injectable results in the same error being displayed:

yarn run v1.22.4
$ NODE_ENV=development nodemon
[nodemon] 1.19.0
...
Aborted (core dumped)
[nodemon] app crashed - waiting for file changes before starting...

Answer №1

My app.module.ts contains a working script for Postgres, but adapting it for MySQL is quite simple. Occasionally, I have to manually remove the dist folder before rebuilding when the database fails to 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],
    }),

The .env file should be in the root folder with the following contents:

DATABASE_USER=
DATABASE_PASS=
DATABASE_HOST=
DATABASE_NAME=
DATABASE_PORT=

Answer №2

In my database module file 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 {}

In my app module file 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 implementation is working perfectly for me

Answer №3

Dealing with a similar issue, I found a solution by adding the following code:

import 'dotenv/config';

Within my app.module.ts file:

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 {}

In addition, my ormconfig is structured as follows:

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

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;

Answer №4

Make sure to change your TypeOrmConfigService from being a @Module() to a @Injectable(). Other than that, everything seems in order.

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'),
    };
  }
}

Answer №5

Here's a suggestion to solve your issue:

// main.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 MainAppModule {}

Answer №6

Not sure if this information is still up to date, but based on my understanding, this seems to be the current situation.

The error message "Nest can't resolve..." indicates that an attempt is being made to inject a class using dependency injection, but Nest is unable to locate this particular dependency in its container.

To address this problem, you must include ConfigService (the unresolved dependency) in the Providers section of your app.module.ts file.

It should look something like this:

@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 {}

Answer №7

Effortless Solution

yarn add @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,
        };
      },
    }),
  ],

Answer №8

I have implemented a method that has been highly effective for me. I have structured three specific environment files to cater to different environments: development, testing, and production.

The file names are .env.development,.env.test and .env.production

  1. In the src/configs folder, I introduced the TypeOrmConfigService with the code snippet below:

     import { Injectable } from '@nestjs/common';
     import { ConfigService } from '@nestjs/config';
     import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from '@nestjs/typeorm';
    
     @Injectable()
     export class TypeOrmConfigService implements TypeOrmOptionsFactory {
       constructor(private readonly configService: ConfigService) {}
       createTypeOrmOptions(): TypeOrmModuleOptions {
         // Environment-specific configurations
       }
     }
    
  2. To incorporate the TypeOrmModule and pass its options to the AppModule, I made adjustments in the AppModule:

    @Module({
       imports: [
        // Configuration setup   
       ],
       controllers: [AppController],
       providers: [
        ...
       ],
     })
     export class AppModule implements NestModule {
         // Additional modifications
     }
    
  3. To adjust the scripts in the package.json file based on the environment, I included specific commands like this:

"scripts": {
    // Environment-specific commands
  },

This implementation should function seamlessly.

Answer №9

To begin, start by creating a new file named database.provider.ts. Inside this file, you will need to 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,
    }),
  }),
];

Next step is to import the newly created file into 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 {}

You can add multiple connections in the database.provider.ts file if needed. Don't forget to create a .env file and import the database module into your root module.

Answer №10

If you're looking to streamline your environmental configurations in NestJS, I highly recommend utilizing the "nestjs-easyconfig" package. Personally, I use it for handling multiple .env files such as .env.development and .env.production. It's quite similar to nestjs/config but with some added benefits. For a demonstration, feel free to take a look at my repository on GitHub.

Answer №11

You have the option to transfer it to a different 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 as shown below*

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 represents the folder name and prod.env signifies the environment file, or you can include it directly in app.module.ts as indicated above**

AN ALTERNATIVE METHOD TO UTILIZE ENVIRONMENT SETTINGS 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 {}

Answer №12

For my example, I simply include the following:

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 file, insert the code below into the import section:

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

    TypeOrmModule.forRootAsync({
      useClass: DatabaseConnectionService,
    })]

Answer №13

When incorporating the ConfigModule into your project, it is best practice to only import it within the TypeOrmCoreModule:

TypeOrmModule.forRootAsync({
  imports: [ConfigModule], // ensure ConfigModule is imported
  useClass: TypeOrmConfigService,
}),

Answer №14

To establish a connection with your database, start by creating a .env file in the root directory of your project and input the following information:

DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=your_username
DB_PASSWORD=your_password
DB_DATABASE=your_database

Next, set up a TypeORM configuration file named ormconfig.js at the root level:

module.exports = {
type: 'postgres', // Adjust this based on your database type
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT, 10),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
entities: ['dist/**/*.entity{.ts,.js}'], // Specify your entity files location
synchronize: false, // Enable only during development
};

Then, create a model config module named config.module.ts:

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

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

You can also import the configuration in your app.model.ts:

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

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),
    TypeOrmModule.forRootAsync({
      useFactory: () => ({
        type: process.env.DB_TYPE, // e.g., 'postgres'
        host: process.env.DB_HOST,
        port: parseInt(process.env.DB_PORT, 10),
        username: process.env.DB_USERNAME,
        password: process.env.DB_PASSWORD,
        database: process.env.DB_DATABASE,
        entities: ['dist/**/*.entity{.ts,.js}'],
        synchronize: false, // Enable only during development
      }),
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Answer №15

To access the .env file located outside the src folder, you need to import the dotenv module.

data-source.ts (outside the src)

    import * as dotenv from 'dotenv';
    dotenv.config(); 
    import entities from 'src/common/entities';
    import { DataSource, DataSourceOptions } from 'typeorm';
    
    export const dataSourceOptions: DataSourceOptions = {
      type: 'postgres',
      port: +process.env.DB_PORT,
      username: process.env.DB_USER,
      password: process.env.DB_PASS,
      host: process.env.DB_HOST,
      database: process.env.DB_NAME,
      entities: entities,
      synchronize: false,
      migrations: ['dist/db/migrations/*.js'], //migrations
    };
    
    const dataSource = new DataSource(dataSourceOptions);
    
    export default dataSource;

app.module

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

Answer №16

It is important to set your ConfigService as a GLOBAL variable

Answer №17

The TypeOrmModuleAsyncOptions feature comes with an added extraProviders property that offers a precise solution to the problem at hand. You can implement this by using the code snippet below.

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

@Injectable()
export class DatabaseFactoryService implements TypeOrmOptionsFactory {
  constructor(private readonly config: ConfigService) {}
  createTypeOrmOptions(
    connectionName?: string
  ): TypeOrmModuleOptions | Promise<TypeOrmModuleOptions> {
    return {
      name: connectionName,
      type: 'better-sqlite3',
      database: this.config.get('DB_NAME'),
    };
  }
}

@Module({
  imports: [
    ConfigModule.forRoot({}),
    TypeOrmModule.forRootAsync({
      extraProviders: [ConfigService],
      useClass: DatabaseFactoryService,
    }),
  ],
})
export class AppModule {}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Error message: Unable to access property 'post' from undefined - Angular 2

Here is the snippet of code in my component file: import { Component, Injectable, Inject, OnInit, OnDestroy, EventEmitter, Output } from '@angular/core'; import { Http, Response, Headers, RequestOptions } from '@angular/http'; import & ...

Tips for fetching individual item information from Firebase real-time database using Angular 9 and displaying it in an HTML format

product.component.ts import { AngularFireDatabase } from '@angular/fire/database'; import { ProductService } from './../product.service'; import { ActivatedRoute } from '@angular/router'; import { Component, OnInit} from &apo ...

What is the best way to pass props to a styled component (e.g., Button) in Material-UI

One of my tasks involves creating a customized button by utilizing the Button component with styled components. export const CustomButton = styled(Button)({ borderRadius: "17px", fontWeight: 300, fontSize: ".8125rem", height: &q ...

Angular, perplexed by the output displayed in the console

I'm completely new to Angular and feeling a bit lost when it comes to the console output of an Angular app. Let me show you what I've been working on so far! app.component.ts import { Component } from '@angular/core'; @Component({ ...

React-hook-form does not display the input length in a custom React component

Introducing a custom Textarea component designed for reusability, this basic textarea includes a maxlength prop allowing users to set the maximum input length. It also displays the current input length in the format current input length/max length. While ...

Type of result from a function that returns a linked promise

In my class, I have a method that returns a chained promise. The first promise's type is angular.IPromise<Foo>, and the second promise resolves with type angular.IPromise<Bar>. I am perplexed as to why the return type of doSomething is an ...

Leveraging Json data in Angular components through parsing

I am currently developing an angular application where I need to retrieve and process data from JSON in two different steps. To start, I have a JSON structure that is alphabetically sorted as follows: { "1": "Andy", "2": &qu ...

Sending binary information from a .net core web api to a typescript application

I currently have a .net core 3.0 web api application connected to an angular 8 client. While I have successfully transferred data between them using json serialization, I am now looking for a way to transfer a bytes array from the api to the client. After ...

Dealing with Dependency Injection Problem in Angular 6

I am grappling with a challenge in my cross-platform Angular application. The crux of the issue is determining the platform on which the app is running and accordingly injecting services. Below is how I've structured it: @NgModule({ providers: [ ...

An issue was encountered in the node_modules folder while attempting to access the 'Exclude' name in the lodash collection file. The error message reads: (1783,24): error TS2304: Cannot

When attempting to execute the ng serve command, I encountered an error. See below for more details. ERROR in node_modules/@types/lodash/common/collection.d.ts(1783,24): error TS2304: Cannot find name 'Exclude'. ... (error list continued) .. ...

'The object of type '{}' does not support indexing with a 'string'

I am currently working on a React component that dynamically generates an HTML Table based on an array of objects. The columns to be displayed are specified through the property called tableColumns. While iterating through the items and trying to display ...

When incorporating an array as a type in Typescript, leverage the keyof keyword for improved

I am facing a situation where I have multiple interfaces. These are: interface ColDef<Entity, Field extends keyof Entity> { field: Field; valueGetter(value: Entity[Field], entity: Entity): any } interface Options<Entity> { colDefs ...

Retrieve the observable value and store it in a variable within my Angular 13 component

Incorporating Angular 13, my service contains the following observable: private _user = new BehaviorSubject<ApplicationUser | null>(null); user$ = this._user.asObservable(); The ApplicationUser model is defined as: export interface ...

How to customize Material UI Autocomplete options background color

Is there a way to change the background color of the dropdown options in my Material UI Autocomplete component? I've checked out some resources, but they only explain how to use the renderOption prop to modify the option text itself, resulting in a a ...

Creating a canvas that adjusts proportionally to different screen sizes

Currently, I am developing a pong game using Angular for the frontend, and the game is displayed inside an HTML canvas. Check out the HTML code below: <div style="height: 70%; width: 70%;" align="center"> <canvas id=&q ...

Passing data between Angular 2 components

Below is the component I am working with: @Component({ selector: 'myselector', providers: [ ], directives: [ ChildComponent], pipes: [ ], template: '<myselector>This is {{testEmitter}}</myselector>' }) export cla ...

Retrieve various data types through a function's optional parameter using TypeScript

Creating a custom usePromise function I have a requirement to create my own usePromise implementation. // if with filterKey(e.g `K=list`), fileNodes's type should be `FileNode` (i.e. T[K]) const [fileNodes, isOk] = usePromise( () => { ...

Retrieve all elements within an Angular6 Directive that share the same name as the Directive

I have created a custom Directive called isSelected and applied it to several elements in different components as shown below: <i isSelected class="icon"></i> <i isSelected class="icon"></i> <i isSelected class="icon"></i ...

Transform JSON into a TypeScript interface with a specialized Date method

Within my Angular 7 project, there is a Post Model defined as follows: export interface PostModel { id: number; created: Date; published: boolean; title: string; } I have implemented an Angular service method aimed at retrieving posts: public g ...

Trigger on the cancellation or completion of a nested observable

I'm seeking a way to detect if an inner observable was not successfully completed (due to everyone unsubscribing) and then emit a value in that scenario. Something akin to defaultIfEmpty, but the current solution isn't effective. A trigger exis ...