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

Is it possible to pass multiple parameters in Angular by utilizing the click() function?

Is there a method for passing parameters with click() in Angular? <a asp-action="CreateSales" (click)="CreateSales(productname='pa', price='16.5')">Some Text</a> I am still learning Angular and would appreciat ...

Struggling to make even the most basic example work with TypeScript and npm modules

After stumbling upon this repository that made using npm modules within a Typescript program look easy, I decided to give it a try by forking it and making some changes. My goal was to add another package to get a better understanding of the process. So, I ...

Is it possible to retrieve props in Vue without using methods?

<script lang='ts'> import GraphWorld from '@/components/GraphWorld.vue' // import { defineComponent } from "vue" export default { name: 'GraphView', props: ['people', 'prac'], compone ...

Verifying TypeScript errors before each commit in a Vue application

We have set up a git hook in our app using Husky for pre-commit actions. Whenever someone commits code, it triggers the pre-commit code - #!/bin/sh . "$(dirname "$0")/_/husky.sh" export NVM_DIR="$HOME/.nvm" [ -s "$NVM_ ...

Is it possible to establish role-based access permissions once logged in using Angular 6?

Upon logging in, the system should verify the admin type and redirect them to a specific component. For example, an HOD should access the admi dashboard, CICT should access admin2 dashboard, etc. Below is my mongoose schema: const mongoose = require(&apo ...

Encountering a Npm ERR! when deploying Angular 6 to Heroku due to missing Start script

I am experiencing an issue with my simple angular 6 app after deploying it to Heroku. When I check the logs using the command heroku logs, I encounter the following error: 2018-07-15T00:45:51.000000+00:00 app[api]: Build succeeded 2018-07-15T00:45:53.9012 ...

Deactivate the button while the form is being submitted

I need a way to prevent users from clicking the submit button multiple times while the form is being processed by the server. Below is the solution I have come up with: clear() { this.count++ this.formGroup.get('name').reset(null); ...

Restrict the properties of an object to match the properties of a different object

I am currently developing an Object patching utility function with the following code snippet class Test{ a:number; b:number; } var c:Test={a:0,b:1} function patchable<T>(obj:T){ return { patch:function<K>(prop:K){ return patc ...

What is the best way to show the previous month along with the year?

I need help with manipulating a date in my code. I have stored the date Nov. 1, 2020 in the variable fiscalYearStart and want to output Oct. 2020. However, when I wrote a function to achieve this, I encountered an error message: ERROR TypeError: fiscalYear ...

Is it necessary to use Generics in order for a TypeScript `extends` conditional type statement to function properly?

Looking to improve my understanding of the extends keyword in TypeScript and its various uses. I recently discovered two built-in utilities, Extract and Exclude, which utilize both extends and Conditional Typing. /** * Exclude from T those types that are ...

What is the best method to create a TypeScript dictionary from an object using a keyboard?

One approach I frequently use involves treating objects as dictionaries. For example: type Foo = { a: string } type MyDictionary = { [key: string]: Foo } function foo(dict: MyDictionary) { // Requirement 1: The values should be of type Foo[] const va ...

Defining TypeScript class events by extending EventEmitter

I have a class that extends EventEmitter and is capable of emitting the event hello. How can I properly declare the on method with a specific event name and listener signature? class MyClass extends events.EventEmitter { emitHello(name: string): void ...

Tips for generating a fixed-length array from multiple arrays with different lengths, focusing on selecting items from each array according to their significance

In order to create a quiz, I am looking to extract 'questions' from various 'topic' arrays. These topics are selected based on the user's preference and are used to populate a question bank for a 20-question quiz. The topics rated ...

Having issues with NGXS subscription not functioning properly when selecting a variable

Currently, I am working with Angular 11 and NGXS. One issue I am facing involves a subscription for a variable in the state. Here is the problematic subscription: @Select(state => state.alert.alerts) alerts$: Observable<any[]> ngOnInit(): void { t ...

Encountering an issue of THREE.EventDispatcher being undefined while trying to create a THREE.OrbitControls instance using THREE.js, THREE.OrbitControls, and TypeScript

Attempting to delve into typescript alongside three.js, I've encountered a perplexing error that has me stumped. The root of the issue lies within the init function where I initialize a new THREE.OrbitControls controller. Utilizing the setup provided ...

Using TypeScript to asynchronously combine multiple Promises with the await keyword

In my code, I have a variable that combines multiple promises using async/await and concatenates them into a single object const traversals = (await traverseSchemas({filename:"my-validation-schema.json"}).concat([ _.zipObject( [&quo ...

Updating a component's value in Angular 6 when there is a change in the corresponding service

My objective sounds straightforward, but I am struggling to implement it: I want one of my components to automatically update when a variable in a service changes. To illustrate my issue, consider the following example: Imagine having a service that incr ...

Converting API response into a class instance using `class-transformer` in TypeScript: A step-by-step guide

When working with TypeScript, I have a regular method called Request(method: HttpMethod, url: string, ...) that is used for calling APIs. Now, my goal is to convert the response from this API request into an instance of a class using class-transformer (or ...

What are some creative ways to emphasize certain dates?

Is there a way to customize mui-x-date-pickers to highlight specific days from a Date array with green filled circles around them? I am using new Date and wondering how to achieve this effect. Below is the code snippet I am currently working with: <Dat ...

Updating data in Angular reactive forms using asynchronous dropdowns

I am currently developing an Angular application and have encountered an issue that I am unable to resolve by solely reading the documentation. One of the components in my application is responsible for displaying a form: https://i.stack.imgur.com/p5KEU. ...