Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typeorm fails to work in HMR #755

Closed
zhmushan opened this issue Jun 1, 2018 · 14 comments

Comments

@zhmushan
Copy link

commented Jun 1, 2018

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:

@FrancisVarga

This comment has been minimized.

Copy link

commented Jun 1, 2018

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.

@unlight

This comment has been minimized.

Copy link

commented Jun 1, 2018

Try to provide new keepConnectionAlive: true setting

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

@nestjs/typeorm 3+

@unlight

This comment has been minimized.

Copy link

commented Jun 1, 2018

@jekirl

This comment has been minimized.

Copy link

commented Jun 1, 2018

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

@zhmushan

This comment has been minimized.

Copy link
Author

commented Jun 2, 2018

@unlight It still does not work

@zhmushan

This comment has been minimized.

Copy link
Author

commented Jun 2, 2018

@unlight I use @nestjs/typeorm 5

@unlight

This comment has been minimized.

Copy link

commented Jun 2, 2018

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 { }
@unlight

This comment has been minimized.

Copy link

commented Jun 2, 2018

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

@zhmushan

This comment has been minimized.

Copy link
Author

commented Jun 2, 2018

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

@unlight

This comment has been minimized.

Copy link

commented Jun 2, 2018

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

@zhmushan zhmushan closed this Jun 2, 2018

skywalker512 added a commit to skywalker512/cqupt-user that referenced this issue May 18, 2019

@zenozen

This comment has been minimized.

Copy link
Contributor

commented May 29, 2019

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 {}
@marktran

This comment has been minimized.

Copy link

commented Jun 1, 2019

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.

@zenozen

This comment has been minimized.

Copy link
Contributor

commented Jun 3, 2019

@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)

@chnirt

This comment has been minimized.

Copy link

commented Jun 23, 2019

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
7 participants
You can’t perform that action at this time.