Search code examples
nestjstypeormdatabase-migrationnestjs-confignestjs-typeorm

How to setup migrations along with ConfigModule in Nestjs


I am trying to figure out a way to setup migrations with typeOrm 0.3.12 in Nestjs along with ConfigModule.The database to be used in this project is Postgres. I have two problems:

  1. When trying to generate migrations, I will get the message that 'No changes in database schema were found'. It happens when I use glob pattern in entities field like this: ['dist/**/*.entity.js'], while when I hardcode them, it works fine.
  2. Cannot find a way to load variables from config when setting up TypeOrmModule because I do not have access to ConfigService outside modules but still need a ormconfig file where I export the data source as it is needed by the migrations scripts.

What I tried in each case:

  1. First I try to setup migrations without ConfigModule to make sure this part works correctly independently. In this situation I cannot have the entities to be read through a glob pattern path and I have to type them one by one. I run the script "typeorm schema:log" which returns that the database already is up to date (I am checking it but it is empty). My entity is the following:
import { Entity, PrimaryGeneratedColumn, Column} from 'typeorm';

@Entity()
export class DoctorEntity {
    @PrimaryGeneratedColumn()
    id:number;

    @Column()
    fullname: string;
}

which I import in its corresponding module like this:

@Module({
  imports: [TypeOrmModule.forFeature([DoctorEntity])],
  controllers: [DoctorsController],
  providers: [DoctorsService]
})
export class DoctorsModule {}

My App Module is the following:

@Module({
  imports: [ConfigModule.forRoot({
    isGlobal: true,
    envFilePath: `.env.${process.env.NODE_ENV}`
  }),
     TypeOrmModule.forRoot(config),
     DoctorsModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

And the config I use is in a file called ormconfig.ts in my src directory of the project, where I also create and export default a data source because it is needed for the migration scripts. Please note that I have tried many different approaches in entities field eg __dirname+'/**/*.entity{.ts,.js}' but with no luck. The ormconfig is the following:

export const config : DataSourceOptions = {
    type:"postgres",
    host: "localhost",
    port: 5432,
    username: "test",
    password: "123",
    database: "test",
    entities: ['dist/**/*.entity.js'],
    synchronize: false,
    migrations: ['./migrations/*.js']
}
const dataSource = new DataSource(config);
export default dataSource;

In my packages.json I have the following scripts regarding the migrations:

"typeorm": "nest build && typeorm-ts-node-commonjs -d ./dist/ormconfig.js",
"migration:generate": "npm run typeorm migration:generate",
"schema:log": "npm run typeorm schema:log"

If i put the DoctorEntity directly inside the entities field in ormconfig, it is working fine but when I use glob pattern I always get the following error: No changes in database schema were found - cannot generate a migration. To create a new empty migration use "typeorm migration:create" command

  1. In order to have migration scripts run I need to provide the data source path in the script as a -d argument as seen in the above package.json, which means that I need to export that data source from some file. But at the same time, in order to use ConfigModule when setting up the TypeOrmModule in AppModule imports,I need to use the forRootAsync function like the following:
TypeOrmModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: (configService: ConfigService) => ({
    type: 'mysql',
    host: configService.get('HOST'),
    port: +configService.get('PORT'),
    username: configService.get('USERNAME'),
    password: configService.get('PASSWORD'),
    database: configService.get('DATABASE'),
    entities: [],
    synchronize: true,
  }),
  inject: [ConfigService],
});

I would still need the ormconfig.ts though from which I export the data source needed by the migration scripts, but I do not have access to the ConfigService there and I am feeling kinda lost... I have spent 3 days searching for a solution to problem but with no luck. So, I thought that it would be "ok" to just pass the entities hardcoded but when I tried to continue I stumbled upon the 2nd problem. Any help will be greatly appreciated. Thanks!


Solution

  • Regarding the first part of the problem, I came across this github issue for typeorm 0.3.12 sprecifically. It appears that the 0.3.12 version is using a version of glob library that is causing the problem. The problem only exists on Windows.The way to fix it: As of current date, downgrading typeorm to version 0.3.11 solves the problem of not being able to load entities with glob pattern path. Since there is an open issue on github, I suppose this will be fixed in next version.

    UPDATE: About the second part of the question, there really is no elegant way to do it. The database connection options will have to be passed through TypeOrmModule.forRootAsync as shown below:

    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (configService: ConfigService) => ({
        type: 'mysql',
        host: configService.get('HOST'),
        port: +configService.get('PORT'),
        username: configService.get('USERNAME'),
        password: configService.get('PASSWORD'),
        database: configService.get('DATABASE'),
        entities: [],
        synchronize: true,
      }),
      inject: [ConfigService],
    });
    

    At the same time there should exist a separate config file that will instantiate a DataSource and export it as default, so that the migration scripts can work. In order to load the variables from the env file - using directly the dotenv library, we will open the file and read it with fs and then pass them manually to the DataSourceOptions object. Something like the following:

    import * as dotenv from 'dotenv';
    import * as fs from 'fs';
    
    const data: any = dotenv.parse(fs.readFileSync(`${environment}.env`));
    
    export const config : DataSourceOptions = {
        type:"postgres",
        host: data.HOST,
        port: data.PORT,
        username: data.USERNAME,
        password: data.PASSWORD,
        database: data.DATABASE,
        entities: ['dist/**/*.entity.js'],
        synchronize: false,
        migrations: ['./migrations/*.js']
    }
    
    export default new DataSource(config);