How to use TypeOrmModule forRootAsync and configService
Posted September 7, 2023
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