How to get values from custom configuration file in Nestjs with interface? - node.js

I'm trying to set up all of my configurations in one file in "config.ts", load it to ConfigService and then with config interfaces get values from it.
So here is my config.ts that have ENV vars from my .env file and static variables.
UPD: Made repo with this example
import { Config } from './config.interface';
const config: Config = {
typeorm: {
type: 'postgres',
host: process.env.DB_HOST,
port: +process.env.DB_PORT,
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
synchronize: process.env.NODE_ENV !== 'prod',
logging: true,
entities: [User, RefreshToken],
},
};
export default () => config;
And here is my interfaces:
export interface Config {
typeorm: TypeOrmConfig;
}
export interface TypeOrmConfig {
type: string;
host: string;
port: number;
username: string;
password: string;
database: string;
synchronize: boolean;
logging: boolean;
entities: any[];
}
The config is loaded to ConfigModule in app.module.ts
#Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: '.dev.env',
load: [config],
}),
}),
For example I want to set up my TypeOrmModule with this setting.
Based on NestJs documentation
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => {
const config = configService.get<TypeOrmConfig>('typeorm');
console.log(config);
return {
type: config.type,
host: config.host,
port: +config.port,
username: config.username,
password: config.password,
database: config.database,
synchronize: config.synchronize,
logging: config.logging,
entities: config.entities,
};
},
inject: [ConfigService],
}),
Here is the problem. Static values from config is okay, but all my ENV vars is undefined. Here is console.log output:
{
type: 'postgres',
host: undefined,
port: NaN,
username: undefined,
password: undefined,
database: undefined,
synchronize: true,
logging: true,
entities: [
[class User extends CoreEntity],
[class RefreshToken extends CoreEntity]
]
}
I don't understand what's the problem with undefined ENV vars
I would be grateful for any explanations and help

In short, NestJS requires you to define a namespace for your custom configuration "typeorm" which you are trying to access here (see Configuration Namespace):
const config = configService.get<TypeOrmConfig>('typeorm');
This means that you must use registerAs function to create your namespace:
import { registerAs } from '#nestjs/config';
import { TypeOrmConfig } from './config.interface';
export default registerAs(
'typeorm',
(): TypeOrmConfig => ({
type: 'postgres',
host: process.env.DB_HOST,
port: +process.env.DB_PORT,
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
synchronize: process.env.NODE_ENV !== 'prod',
logging: true,
entities: [],
}),
);
This will do the trick. However, we can improve upon this.
Instead of defining your own interface for the TypeORM configuration, there are existing interfaces you can use and / or extend from to reduce boilerplate code (below I mention ConnectionOptions though TypeOrmModuleOptions might be more appropriate depending on your configuration needs). I would also suggest providing fallback values to environmental variables:
import { registerAs } from '#nestjs/config';
import { ConnectionOptions } from 'typeorm';
const CONNECTION_TYPE = 'postgres';
export default registerAs(
'typeorm',
(): ConnectionOptions => ({
type: CONNECTION_TYPE,
host: process.env.DB_HOST || 'default_value',
port: +process.env.DB_PORT || 3000,
username: process.env.DB_USERNAME || 'default_value',
password: process.env.DB_PASSWORD || 'default_value',
database: process.env.DB_NAME || 'default_value',
synchronize: process.env.NODE_ENV !== 'prod',
logging: true,
entities: [],
}),
);
Also, there is no need to explicitly assign configuration values to TypeOrmModule again as you can simply:
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) =>
await configService.get('typeorm'),
inject: [ConfigService],
}),
As a personal flavour I like to create configuration enum to contain configuration names to prevent misspelling. Consider this:
export enum ConfigEnum {
TYPEORM = 'typeorm',
}
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) =>
await configService.get<Promise<TypeOrmModuleOptions>>(
ConfigEnum.TYPEORM,
),
inject: [ConfigService],
})
Async / Await pattern is also redundant here, as we do not actually perform anything async, so this will work as well:
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) =>
configService.get<TypeOrmModuleOptions>(ConfigEnum.TYPEORM),
inject: [ConfigService],
})
Welcome to StackOverflow!
Ps. I have made a pull request, see https://github.com/JustDenP/nest-config/pull/1

Related

Typeorm migrations are not working with nest js

I am looking for my new project with nestjs, mysql and typeorm. Everything is working as expected but not I am not able to use migrations in our projects. Also, I did not find any proper documentation for integrating migrations in nests. Here is my project structure
**typeorm.config-migrations.ts**
import { typeOrmConfig } from './typeorm.config';
export = typeOrmConfig;
**typeorm.config.ts**
import { ConfigModule, ConfigService } from '#nestjs/config';
import {
TypeOrmModuleAsyncOptions,
TypeOrmModuleOptions,
} from '#nestjs/typeorm';
export const typeOrmAsyncConfig: TypeOrmModuleAsyncOptions = {
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (): Promise<TypeOrmModuleOptions> => {
return {
type: 'mysql',
host: process.env.MYSQL_HOST,
port: parseInt(process.env.MYSQL_PORT, 10),
username: process.env.MYSQL_USERNAME,
database: process.env.MYSQL_DATABASE_NAME,
password: process.env.MYSQL_PASSWORD,
entities: [__dirname + '/../**/*.entity.{js,ts}'],
migrations: [__dirname + '/../database/migrations/*{.ts,.js}'],
extra: {
charset: 'utf8mb4_unicode_ci',
},
synchronize: false,
logging: true,
};
},
};
export const typeOrmConfig: TypeOrmModuleOptions = {
type: 'mysql',
host: process.env.MYSQL_HOST,
port: parseInt(process.env.MYSQL_PORT, 10),
username: process.env.MYSQL_USERNAME,
database: process.env.MYSQL_DATABASE_NAME,
password: process.env.MYSQL_PASSWORD,
entities: [__dirname + '/../**/*.entity.{js,ts}'],
migrations: [__dirname + '/../database/migrations/*{.ts,.js}'],
extra: {
charset: 'utf8mb4_unicode_ci',
},
synchronize: false,
logging: true,
};
I am integrating this in the app module like below
TypeOrmModule.forRootAsync(typeOrmAsyncConfig),
But I am getting below error and couldn't understand the actual issue.
[Nest] 31010 - 08/14/2022, 8:06:21 PM ERROR [TypeOrmModule] Unable to connect to the database. Retrying (1)...
MissingPrimaryColumnError: Entity "BaseEntity" does not have a primary column. Primary column is required to have in all your entities. Use #PrimaryColumn decorator to add a primary column to your entity.
at EntityMetadataValidator.validate (/home/devuser/Projects/Node/nest-learning-hello-world/src/metadata-builder/EntityMetadataValidator.ts:59:19)
at /home/devuser/Projects/Node/nest-learning-hello-world/src/metadata-builder/EntityMetadataValidator.ts:43:18
at Array.forEach (<anonymous>)
at EntityMetadataValidator.validateMany (/home/devuser/Projects/Node/nest-learning-hello-world/src/metadata-builder/EntityMetadataValidator.ts:42:25)
at DataSource.buildMetadatas (/home/devuser/Projects/Node/nest-learning-hello-world/src/data-source/DataSource.ts:685:33)
at DataSource.initialize (/home/devuser/Projects/Node/nest-learning-hello-world/src/data-source/DataSource.ts:242:13)
query: SELECT VERSION() AS `version`
Also I am not using BaseEntity in my project
Could anyone help to resolve this issue?
Thanks in advance

Nestjs Typeorm query still using 'postgres' database

When I try to query using repository for login api, I receive this error
QueryFailedError: relation "user" does not exist
After debuging, I realize the nestjs still using database with name "postgres". eventhough I set my database name in my .env as "nestjs". (I can confirm because when I migrate and seed the database "postgres" the login function works)
DB_TYPE=postgres
DB_HOST=db
DB_USER=postgres
DB_PASS=postgres
DB_NAME=nestjs
DB_NAME_TEST=nestjs_test
DB_PORT=5432
All my migration and seed process already using database "nestjs". Below is my files to configure my database connection
configuration.ts
import { registerAs } from '#nestjs/config';
export default registerAs('database', () => ({
type: process.env.DB_TYPE,
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASS,
name: process.env.DB_NAME,
nameTest: process.env.DB_NAME_TEST,
port: process.env.DB_PORT,
}));
database/config.service.ts
...
get name(): string {
return this.configService.get<string>(
this.configService.get<string>('app.env') === 'test'
? 'database.nameTest'
: 'database.name',
);
}
...
database/config.migration.ts
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
export = {
type: process.env.DB_TYPE,
host: process.env.DB_HOST,
port: +process.env.DB_PORT,
username: process.env.DB_USER,
password: process.env.DB_PASS,
database:
process.env.NODE_ENV === 'test'
? process.env.DB_NAME_TEST
: process.env.DB_NAME,
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
migrations: [__dirname + '/../../database/migrations/*{.ts,.js}'],
cli: {
migrationsDir: __dirname + '/../../database/migrations',
},
extra: {
charset: 'utf8mb4_unicode_ci',
},
synchronize: false,
logging: true,
keepConnectionAlive: true,
namingStrategy: new SnakeNamingStrategy(),
};
database/provider.module.ts
import { Module } from '#nestjs/common';
import { TypeOrmModule, TypeOrmModuleOptions } from '#nestjs/typeorm';
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
import { DatabaseConfigModule } from '../../config/database/config.module';
import { DatabaseConfigService } from '../../config/database/config.service';
#Module({
imports: [
TypeOrmModule.forRootAsync({
imports: [DatabaseConfigModule],
inject: [DatabaseConfigService],
useFactory: async (
service: DatabaseConfigService,
): Promise<TypeOrmModuleOptions> => ({
type: service.type,
host: service.host,
port: service.port,
username: service.user,
password: service.password,
name: service.name,
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
migrations: [__dirname + '/../../database/migrations/*{.ts,.js}'],
cli: {
migrationsDir: __dirname + '/../../database/migrations',
},
extra: {
charset: 'utf8mb4_unicode_ci',
},
synchronize: false,
logging: true,
keepConnectionAlive: true,
namingStrategy: new SnakeNamingStrategy(),
}),
}),
],
})
export class DatabaseProviderModule {}
And here is my databases:
If you guys need more file for information please let me know. I'm new to nestjs and typeorm, So please tell me what's wrong with my code. Thank you!
TypeormModule has the database property which provides the database name. You are using name in provider.module.ts:
Wrong name : databaseName
Right database : databaseName
TypeOrmModule.forRootAsync({
useFactory: () => ({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
}),
});

How do I connect to Heroku Postgres from Nest.js?

When running heroku config I see (with some omissions)
DATABASE_URL: postgres://<xxxx>:<xxxx>#ec2-xx-xx-xxx-xx.compute-1.amazonaws.com:5432/dm74hmu71b97n
which I know is of form postgres://<user>:<password>#<hostname>:<port>/<database>. But right now in my Nest.JS app I connect to postgres like this:
#Module({
imports: [
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
type: 'postgres',
host: configService.get('POSTGRES_HOST'),
port: configService.get('POSTGRES_PORT'),
username: configService.get('POSTGRES_USER'),
password: configService.get('POSTGRES_PASSWORD'),
database: configService.get('POSTGRES_DB'),
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
synchronize: false,
}),
}),
],
})
export class DatabaseModule {}
I suppose I could manually parse process.env.DATABASE_URL I figured there must be a better/easier way.
useFactory: (configService: ConfigService) => ({
url: configService.get('DATABASE_URL'),
type: 'postgres',
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
synchronize: false,
}),

Reading .env for nestjs app with typeorm having custom provider

I am new to nestJS and I want to setup .env for existing application & facing issue.
I have custom provider for appModule as below,
#Module({
providers: [
AbcService,
XyzService,
],
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'xxxxxxxx',
port: 3230,
username: 'xyz',
password: 'password',
database: 'xyz-db',
entities: [__dirname + '/entities/**/*.entity{.ts,.js}'],
synchronize: true,
migrationsRun: true,
logging: true,
}),
TypeOrmModule.forFeature([
Transaction,
Payment,
]),
KafkaModule.forRoot(serviceConfig),
],
exports: [],
controllers: [ServiceSubscriptionController],
})
export class TopicModule { }
I have imported it inside AppModule as below,
#Module({
imports: [TopicModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
I want to keep these stuff inside .env and I tried it as per documentation as below,
TypeOrmModule.forRootAsync({
imports: [ConfigModule.forRoot({
envFilePath: '.env',
})],
useFactory: async (configService: ConfigService) => {
return {
host: configService.get('HOST'),
type: 'mysql',
port: 3230,
username: 'xyz',
password: 'password',
database: 'xyz-db',
entities: [__dirname + '/entities/**/*.entity{.ts,.js}'],
synchronize: true,
migrationsRun: true,
logging: true,
}
},
inject: [ConfigService]
}),
I have .env at root path with HOST key-value pair as below but it read undefined from it.
In package.json,
"start": "nest start",
"start:dev": "nest start --watch",
It seems that Nest's ConfigModule will run fs.readFileSync(envFilePath) if you pass a file path to the forRoot() method. If you want it to read from the root directory, either remove the envFilePath option, or set the full file path, from your user's home directory.
I have loaded config in main.ts manually as below.
import { config } from 'dotenv';
async function bootstrap() {
//factory method for normal TS app
await config();
const app = await NestFactory.create(AppModule);
Now I can access it as,
configService.get('HOST') // as provided in question
or as process.env.HOST
Note: I have to use forRootAsync instead of forRoot to access process.env

How to tear down MikroOrm in NestJS

I've recently converted my AppModule to a dynamic module so that I'm able to provide different configurations to MikroOrm depending on context (E2E tests, etc) and it currently looks like this:
#Module({
imports: [
MikroOrmModule.forFeature({
entities: [Todo],
}),
],
providers: [TodoService],
controllers: [AppController, TodosController],
})
export class AppModule {
static register(options?: {
mikroOrmOptions?: MikroOrmModuleOptions;
}): DynamicModule {
return {
module: AppModule,
imports: [
MikroOrmModule.forRoot({
entities: [Todo],
type: 'postgresql',
host: process.env.DB_HOST,
port: process.env.DB_PORT ? parseInt(process.env.DB_PORT) : 5432,
user: process.env.DB_USER,
password: process.env.DB_PASS,
dbName: process.env.DB_DB,
...options?.mikroOrmOptions,
}),
],
};
}
}
Now I'm trying to ensure graceful shutdown of the app by disconnecting from the database, but not sure where to place a life-cycle hook in this case. It doesn't seem to be possible to have a dynamic module with life-cycle hooks, so I'm thinking of developing a separate provider that injects the orm and write the hook on that.
What would be the correct approach? Thanks.
Edit:
I came up with the following solution. Would appreciate someone indicating if this is the best way:
import { MikroORM } from 'mikro-orm';
...
#Module({
imports: [
MikroOrmModule.forFeature({
entities: [Todo],
}),
],
providers: [TodoService],
controllers: [AppController, TodosController],
})
export class AppModule implements OnModuleDestroy {
static register(options?: {
mikroOrmOptions?: MikroOrmModuleOptions;
}): DynamicModule {
return {
module: AppModule,
imports: [
MikroOrmModule.forRoot({
entities: [Todo],
type: 'postgresql',
host: process.env.DB_HOST,
port: process.env.DB_PORT ? parseInt(process.env.DB_PORT) : 5432,
user: process.env.DB_USER,
password: process.env.DB_PASS,
dbName: process.env.DB_DB,
...options?.mikroOrmOptions,
}),
],
};
}
constructor(private orm: MikroORM) {}
async onModuleDestroy(): Promise<void> {
await this.orm.close();
}
}
As discussed in the issues, I would go with the way nestjs/typeorm is using, so using onApplicationShutdown hook.
Also linking the issue here for possible future readers:
https://github.com/dario1985/nestjs-mikro-orm/issues/10

Resources