Nest: Typeorm fails to work in HMR

Created on 1 Jun 2018  路  15Comments  路  Source: nestjs/nest

Cannot connect to database when using hmr


[ ] Regression 
[x] Bug report
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.

Current behavior


After creating a new project, I directly use the document example to connect to mongodb.
If I use npm run start, it works.
if I use npm run webpack && npm run start:hmr, it will fail, as shown below
image

// ormconfig.json
{
  "type": "mongodb",
  "host": "localhost",
  "database": "polaris",
  "entities": [
    "src/**/**.entity.ts"
  ],
  "synchronize": true
}

Environment


Nest version: 5.0.0


For Tooling issues:
- Node version: 8.11.1  
- Platform:  Windows 10

Others:

Most helpful comment

How do you go about registering the entities then? given that a glob import doesn't work?

Yes, glob pattern will not work.
You must provide class references to entities field instead.

import { Cat } from '../cat/cat.entity';

@Module({
    imports: [
        // provides: typeorm/Connection, typeorm/EntityManager
        TypeOrmModule.forRoot({
            entities: [
                Cat,
            ],
        }),
    ],
})
export class DatabaseModule { }

But fortunately webpack has feature require.context which allow to collect needed files.

// entity.context.ts (in root src folder)
export const entityContext =  require.context('.', true, /\.entity\.ts$/);
// database.module.ts
import { entityContext } from '../entity.context';

@Module({
    imports: [
        TypeOrmModule.forRoot({
            entities: [
                ...entityContext.keys().map(id => {
                    const entityModule = entityContext(id);
                    // We must get entity from module (commonjs)
                    // Get first exported value from module (which should be entity class)
                    const [entity] = Object.values(entityModule);
                    return entity;
                })
            ],
        }),
    ],
})
export class DatabaseModule { }

All 15 comments

Miss Read...

I think u having an issue to connect to your MongoDb database, it seems like.

Another thing i don't think the:

(node:60515) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect.

It has something todo with nest, this is directly typeOrm problem.

Try to provide new keepConnectionAlive: true setting

@Module({
    imports: [
        TypeOrmModule.forRoot({
            keepConnectionAlive: true,
            // ...
        }),
    ],
})
export class DatabaseModule { }

@nestjs/typeorm 3+

711

@unlight How do you go about registering the entities then? given that a glob import doesn't work?

@unlight It still does not work

@unlight I use @nestjs/typeorm 5

How do you go about registering the entities then? given that a glob import doesn't work?

Yes, glob pattern will not work.
You must provide class references to entities field instead.

import { Cat } from '../cat/cat.entity';

@Module({
    imports: [
        // provides: typeorm/Connection, typeorm/EntityManager
        TypeOrmModule.forRoot({
            entities: [
                Cat,
            ],
        }),
    ],
})
export class DatabaseModule { }

But fortunately webpack has feature require.context which allow to collect needed files.

// entity.context.ts (in root src folder)
export const entityContext =  require.context('.', true, /\.entity\.ts$/);
// database.module.ts
import { entityContext } from '../entity.context';

@Module({
    imports: [
        TypeOrmModule.forRoot({
            entities: [
                ...entityContext.keys().map(id => {
                    const entityModule = entityContext(id);
                    // We must get entity from module (commonjs)
                    // Get first exported value from module (which should be entity class)
                    const [entity] = Object.values(entityModule);
                    return entity;
                })
            ],
        }),
    ],
})
export class DatabaseModule { }

But I think this way is not good for production, but acceptable in development.

Okay, but this is still not elegant, I very much hope that nestjs can fix this problem

It is not problem of nest. It is about webpack, if you want to use glob pattern, use ts-node-dev

Putting my two cents here for people with the same problem.

The reason this is happening is because

  1. webpack bundles all the code into a separate bundle file, and in order for HMR to work, this bundle files is loaded and run as the server
  2. specifying entities: [__dirname + '/**/*.ts'] (or .js in my case) would cause typeorm to require those files (while the real entities is already loaded in the webpack bundle).
  3. when typeorm tries to get repository, it compares the entity passed-in to getRepository (for example, getRepository(User), where this User is loaded from the webpack bundle), with the ones loaded in the entities config, which is loaded from js/ts files.
  4. Even though they have the same name, they're two different class (functions) loaded from different modules, so typeorm will not be able to find the correct one.

My workaround is based on the fact that all the modules are loaded, hence all the entities should be loaded already, via imports. This is especially true in NestJS, with the well-structured project. Specifically, for each module, either the module itself or the controller will import the entity somewhere.

By leveraging the internal mechanism of @Entity decorator, we'll be able to get a list of entity classes.

Not sure how true this is, but seems to work well with both dev and prod settings.

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { getMetadataArgsStorage } from 'typeorm';

// The modules are loaded from here, hence importing the entities
import { AModule } from './a/a.module';
import { BModule } from './b/b.module';

@Module({
  imports: [
    AModule, 
    BModule, 
    TypeOrmModule.forRoot({ 
      ...,
      entities: getMetadataArgsStorage().tables.map(tbl => tbl.target),
      migrations: ...,
    }),
  ]
})
export class AppModule {}

Thanks @zenozen. That solution is working well in my development environment.

Have you seen any issues with your tests? getMetadataArgsStorage() doesn't seem to pick up any tables in my test environment when running jest even though there are modules/entities being imported.

import { Test, TestingModule } from '@nestjs/testing';
import { BootstrapModule } from '../bootstrap/bootstrap.module';
import { DocumentationModule } from './documentation.module';
import { DocumentationService } from './documentation.service';

import seed from '../../test/seeds';

describe('DocumentationService', () => {
  let module: TestingModule;
  let service: DocumentationService;
  let fixtures: any;

  beforeAll(async () => {
    module = await Test.createTestingModule({
      imports: [
        BootstrapModule,
        DocumentationModule,
      ],
    }).compile();

    service = module.get<DocumentationService>(DocumentationService);
    fixtures = await seed();
  });

  afterAll(async () => {
    module.close();
  });

  describe('generateLink', () => {
    it('returns a link', async () => {
      const url = await service.generateLink(fixtures.user);
      expect(url).toContain(
        '[redacted]',
      );
    });
  });
});
RepositoryNotFoundError: No repository for "Organization" was found. Looks like this entity is not registered in current "default" connection?

OrganizationModule is imported in DocumentationModule. Works fine outside of jest.

@marktran I don't have testing yet in my project, fairly new codebase.
I would be happy to debug this if there's a small reproducible example. Or maybe later after I have proper testing :)

I would probably look at, 1. the stack trace of the error; 2. the result of getMetadataArgsStorage and whether the Entity decorator has been run (by rolling your own decorator like the linked typeorm issue suggested, or using some debugging techniques)

Putting my two cents here for people with the same problem.

The reason this is happening is because

  1. webpack bundles all the code into a separate bundle file, and in order for HMR to work, this bundle files is loaded and run as the server
  2. specifying entities: [__dirname + '/**/*.ts'] (or .js in my case) would cause typeorm to require those files (while the real entities is already loaded in the webpack bundle).
  3. when typeorm tries to get repository, it compares the entity passed-in to getRepository (for example, getRepository(User), where this User is loaded from the webpack bundle), with the ones loaded in the entities config, which is loaded from js/ts files.
  4. Even though they have the same name, they're two different class (functions) loaded from different modules, so typeorm will not be able to find the correct one.

My workaround is based on the fact that all the modules are loaded, hence all the entities should be loaded already, via imports. This is especially true in NestJS, with the well-structured project. Specifically, for each module, either the module itself or the controller will import the entity somewhere.

By leveraging the internal mechanism of @Entity decorator, we'll be able to get a list of entity classes.

Not sure how true this is, but seems to work well with both dev and prod settings.

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { getMetadataArgsStorage } from 'typeorm';

// The modules are loaded from here, hence importing the entities
import { AModule } from './a/a.module';
import { BModule } from './b/b.module';

@Module({
  imports: [
    AModule, 
    BModule, 
    TypeOrmModule.forRoot({ 
      ...,
      entities: getMetadataArgsStorage().tables.map(tbl => tbl.target),
      migrations: ...,
    }),
  ]
})
export class AppModule {}

it works for me

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings