How to use TypeOrmModule forRootAsync and configService

Posted September 7, 2023
How to use TypeOrmModule forRootAsync and configService

TypeOrmModule uses the forRootAsync method whenever you want to configure the TypeORM module asynchronously. In this post, I will guide you through the steps of adding TypeOrmModule.forRootAsync method to NestJS and configure your TypeORM module in conjunction with ConfigService.

This tutorial teaches how to perfectly use forRootAsync with TypeOrmModule alongside NestJS.

Step 1: When do you need TypeOrmModule forRootAsync

By default, TypeORM uses forRoot method to add the global TypeORM settings as follows:

// Configure TypeOrmModule at the root of the application
TypeOrmModule.forRoot({
  // database type
  type: 'mysql',
  // Database connection details
  host: 'localhost', //server host
  // mysql server port
  port: 3306,
  // Database username
  username: 'username',
  // Database user password
  password: 'password',
  // Database name
  database: 'database_name',
  // The entities path (database tables) to be synchronized
  entities: [__dirname + '/**/*.entity{.ts,.js}'],
  synchronize: true,
}),

If you want to fetch configuration values from an asynchronous source, such as a database, a .env file, or environment variables, forRoot won’t work.

You need to use TypeOrmModule with forRootAsync to asynchronously load TypeORM configuration. This way, the services being loaded must have a method that returns a Promise resolving to the TypeORM configurations.

Related: Guide to TypeOrmModule forFeature and forRoot Methods

Step 2: How to use forRootAsync with AppModule

As we have explained in the above section, you normally load TypeOrmModule using TypeOrmModule.forRoot method.

To add asynchronous option using forRootAsync, you need to use the useFactory property as a factory function as follows:

@Module({
  imports: [
    // Configure TypeOrmModule using an async factory function
    // check forRootAsync
    TypeOrmModule.forRootAsync({
      useFactory: async () => {
        // async configuration options
        return {
          type: "mysql",
          host: "localhost",
          port: 3306,
          username: "db_username",
          password: "user_password",
          database: "db_name",
          entities: [],
        };
      },
    }),
  ],
})

Step 3: Using TypeOrmModule forRootAsync with DatabaseModule

Suppose you have a DatabaseModule. In this case, you have used DatabaseModule to configure TypeORM asynchronously to get database connection details. You’ll have a method to return a Promise and resolve the configuration.

Here, you’ll assume you created a database config file, database-config.service.ts, and implemented a Promise to fetch the configuration asynchronously as follows:

import { Injectable } from "@nestjs/common";
@Injectable()
// Asynchronously retrieve database configuration
export class DatabaseConfigService {
  async getDatabaseConfig(): Promise<any> {
    return {
      type: "mysql",
      host: "localhost",
      // other database connection details
    };
  }
}

Now, you’ll use this custom DatabaseConfigService in your AppModule. But because it returns a Promise, the exported DatabaseConfigService become asynchronous. TypeOrmModule will need forRootAsync to access it and not forRoot.

In the app.module.ts file, you’ll import DatabaseConfigService and the Appmodule will dynamically retrieve database configuration with forRootAsync and look as follows:

import { DatabaseConfigService } from "./database-config.service";
@Module({
  imports: [
    // Add class to use for dynamically retrieving database configuration with forRootAsync
    TypeOrmModule.forRootAsync({
      //Now add the reference to the configuration service
      useClass: DatabaseConfigService,
    }),
  ],
})

Step 4: The Right Way to use TypeOrmModule forRootAsync with ConfigModule and ConfigService

What if you’re using env files to manage environment variables within NestJS? Nest.js uses a Config Module to add and load environment variables using @nestjs/config.

Here, you must have @nestjs/config installed.

npm install @nestjs/config

Then use an .env file with database environment variables as follows:

DATABASE_SERVER = localhost;
DATABASE_PORT = 3306;
TEST_USERNAME = example_username;
USER_PASS = username_pass;
TEST_DATABASE = test_database;

To load your variables, create a config.module.ts file. The ConfigModule ensures these configuration values are properly loaded and available in the whole app. ConfigModule will use envFilePath to load the .env file using @nestjs/config. as follows.

import { Module } from "@nestjs/common";
// Grab and import the ConfigModule from the nestjs config module
import { ConfigModule } from "@nestjs/config";

@Module({
  // Import and configure the ConfigModule for application-wide configuration
  imports: [
    ConfigModule.forRoot({
      //  path to the environment file
      envFilePath: ".env",
      // Make the configuration global, accessible throughout the entire application
      isGlobal: true,
    }),
  ],

  // Export the ConfigModule
  exports: [ConfigModule],
})
// Export the module class
export class AppConfigModule {} 

You are now using ConfigModule, and it loads environment variables. TypeORM will create this setup asynchronously.

You’ll then build a ConfigService to access configuration values using the @nestjs/config package. So, create a config.service.ts file to retrieve and .env variables as such:

// To make ConfigService injectable
import { Injectable } from "@nestjs/common";
// add ConfigService from the config package
import { ConfigService as NestConfigService } from "@nestjs/config";
// This service is extended with methods to retrieve and manage configuration values
@Injectable()
export class ConfigService {
  constructor(private nestConfigService: NestConfigService) {}
}

The ConfigService is injected into the useFactory function with TypeOrmModule.forRootAsync. Its part of dynamically using the TypeORM configuration based on values retrieved from the ConfigService.

It is this way that TypeORM asynchronously uses the ConfigService to get database connection details to AppModule with forRootAsync as follows:

// import path for your ConfigService method
import { ConfigService } from "./config.service";

@Module({
  imports: [
    // Configure TypeOrmModule to access DatabaseModule using an async factory function
    TypeOrmModule.forRootAsync({
      // Import the AppConfigModule
      imports: [AppConfigModule],
      // Inject ConfigService to dynamically retrieve configuration
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => ({
        type: "mysql",
        // Retrieve database values from the env configuration
        host: configService.get("DATABASE_SERVER"),
        port: configService.get("DATABASE_PORT"),
        username: configService.get("TEST_USERNAME"),
        password: configService.get("USER_PASS"),
        database: configService.get("TEST_DATABASE"),
      }),
    }),
  ],
})

If you want to explore how to use ConfigModule and ConfigService in deeper detail, I created this Guide to NestJS env variables with ConfigModule and dotenv for you.

Conclusion

I will consider forRootAsync as a dynamic configuration runtime when working with environments that change over time. This example guide showed how TypeOrmModule uses forRootAsync, and you learned something new.

Related: TypeOrmModuleOptions in NestJS Apps

How to use TypeOrmModule forRootAsync and configService

Written By:

Joseph Chege