TypeORM OneToMany causes "ReferenceError: Cannot access '<Entity>' before initialization"
Asked Answered
T

8

23

I have two entities: User and Habit. A user can create multiple Habits, thus I use a OneToMany relation on the User (and ManyToOne on the Habit, respectively).

User Entity

import {Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, BeforeInsert, BeforeUpdate, OneToMany} from "typeorm";
import * as bcrypt from "bcryptjs";
import { Habit } from "../habits/habits.entity";

@Entity()
export class User {
    @PrimaryGeneratedColumn("uuid")
    id: string;

    @Column()
    name: string;

    @Column()
    email: string;

    @Column()
    password: string;

    @OneToMany(type => Habit, habit => habit.author)
    habits: Habit[];

    @CreateDateColumn()
    dateCreated: Date;

    @UpdateDateColumn()
    dateUpdated: Date;

    @BeforeInsert()
    @BeforeUpdate()
    async hashPassword(): Promise<void> {
        this.password = await bcrypt.hash(this.password,10);
    }

    async comparePassword(password: string): Promise<boolean> {
        return bcrypt.compare(password, this.password);
    }

    constructor(props: any) {
        Object.assign(this, props);
    }
}

Habit Entity

import {Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn, ManyToOne} from "typeorm";
import { User } from "../users/users.entity";

@Entity()
export class Habit {
    @PrimaryGeneratedColumn("uuid")
    id: string;

    @Column()
    name: string;

    @Column({ nullable: true})
    description?: string;

    @ManyToOne(type => User, user => user.habits)
    author: User;

    @CreateDateColumn()
    dateCreated: Date;

    @UpdateDateColumn()
    dateUpdated: Date;

    constructor(props: Partial<Habit>) {
        Object.assign(this, props);
    }
}

Problem

When setting up the above relation I receive the following error

WARNING in Circular dependency detected:
apps\api\src\habits\habits.entity.ts -> apps\api\src\users\users.entity.ts -> apps\api\src\habits\habits.entity.ts

WARNING in Circular dependency detected:
apps\api\src\users\users.entity.ts -> apps\api\src\habits\habits.entity.ts -> apps\api\src\users\users.entity.ts


/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "User", function() { return User; });
                                                                                               ^
ReferenceError: Cannot access 'User' before initialization
    at Module.User (...\dist\apps\api\main.js:1782:96)
    at Module../apps/api/src/habits/habits.entity.ts (...\dist\apps\api\webpack:\apps\api\src\habits\habits.entity.ts:42:13)
    at __webpack_require__ (...\dist\apps\api\webpack:\webpack\bootstrap:19:1)
    at Module../apps/api/src/users/users.entity.ts (...\dist\apps\api\main.js:1790:79)
    at __webpack_require__ (...\dist\apps\api\webpack:\webpack\bootstrap:19:1)
    at Module../apps/api/src/config/db-config.service.ts (...\dist\apps\api\main.js:1038:77)
    at __webpack_require__ (...\dist\apps\api\webpack:\webpack\bootstrap:19:1)
    at Module../apps/api/src/config/config.module.ts (...\dist\apps\api\main.js:978:76)
    at __webpack_require__ (...\dist\apps\api\webpack:\webpack\bootstrap:19:1)
    at Module../apps/api/src/app/app.module.ts (...\dist\apps\api\main.js:147:79)

Note

I use Nx and have created a NestJS app. The TypeOrm version is "^0.2.22" and the @nestjs/typeorm version is "^6.2.0"

My tsconfig is as follows:

{
  "compileOnSave": false,
  "compilerOptions": {
    "rootDir": ".",
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "importHelpers": true,
    "target": "es2015",
    "module": "esnext",
    "typeRoots": ["node_modules/@types"],
    "lib": ["es2018", "dom"],
    "skipLibCheck": true,
    "skipDefaultLibCheck": true,
    "baseUrl": ".",
    "paths": {
      "@awhile/contracts": ["libs/contracts/src/index.ts"],
      "@awhile/ui": ["libs/ui/src/index.ts"]
    }
  },
  "exclude": ["node_modules", "tmp"]
}

I have tried to use a ManyToMany relation and it worked. Also, on a separate NestJS app (without Nx) I cannot reproduce this reference error. Changing the ECMAScript target version in the tsconfig.json also did not work.

Both entities are only used in their services and are not instantiated anywhere else.

I appreciate any help. Thank you in advance.

Troopship answered 23/2, 2020 at 14:44 Comment(0)
D
5

I solved this by using autoLoadEntities: true when loading the TypeORM config to NestJS. Note that this is a NestJS extra, so if you are using ormconfig.json this property won't be applied.

autoLoadEntities documentation here: https://docs.nestjs.com/techniques/database#auto-load-entities

UPDATE 04/10/2020

I kept having the same issue with relations. Another solution I found, even if it breaks some standards is to add all the Entities into 1 file. Export them there and import them where needed.

Keep in mind that the order in which the classes are declared matters.

Dasha answered 1/4, 2020 at 18:28 Comment(1)
This is indeed the case, after reordering my classes error disappeared. This is wrong, it shouldn't depend on the order, as with any javascript code where hoisting works.Micropaleontology
D
42

Use the Relation type wrapper to avoid circular dependency issues, as described here.

For example:

import {Entity, OneToMany, Relation} from "typeorm";
import {Habit} from "../habits/habits.entity";

@Entity()
export class User {
    ...

    @OneToMany(type => Habit, habit => habit.author)
    habits: Relation<Habit>[];

    ...
}
Dory answered 23/4, 2022 at 20:55 Comment(2)
Adding Relation<...> helped when I moved the project to use ESM modules.Peart
It works with OneToOne as well. Greate!Gherkin
D
8

In tsconfig, setting the target to "esnext" and module to "commonjs" fixed the issue for me

{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "types": ["node", "jest"],
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "module": "commonjs",
    "target": "esnext"
  },
  "include": ["**/*.ts"]
}
Density answered 15/4, 2020 at 19:54 Comment(2)
I tried to get a vue electron app to work with typeorm. Error was: ReferenceError, can not use variable before declaration on an entity. That did the trick for me. Thanks!Consequent
Using this on an auto generated schema. Lots of hand work, but much less than creating the schema from scratch;Tourcoing
D
5

I solved this by using autoLoadEntities: true when loading the TypeORM config to NestJS. Note that this is a NestJS extra, so if you are using ormconfig.json this property won't be applied.

autoLoadEntities documentation here: https://docs.nestjs.com/techniques/database#auto-load-entities

UPDATE 04/10/2020

I kept having the same issue with relations. Another solution I found, even if it breaks some standards is to add all the Entities into 1 file. Export them there and import them where needed.

Keep in mind that the order in which the classes are declared matters.

Dasha answered 1/4, 2020 at 18:28 Comment(1)
This is indeed the case, after reordering my classes error disappeared. This is wrong, it shouldn't depend on the order, as with any javascript code where hoisting works.Micropaleontology
S
5

You can accomplish the same thing as @jdruper by using a barrel file. If you don't have a paths property in your tsconfig.json, add one under compilerOptions that looks something like this:

"paths": {
   "@entities":["app/src/entities/index.ts"]
}

Put all your entity classes in the entities folder, create the index.ts file and add export statements in the index.ts file:

export * from './user.entity';
export * from './habit.entity';

Make sure the export statements appear in the required order. Your imports would then look like this.

import { User, Habit } from '@entities';

You can search for typescript barrel file for more information on how to work with barrel files.

If you're using NX you can make an entities lib and accomplish the same thing.

It will probably mean having to move all your entity files together and updating the imports in your modules and services. Maybe not exactly ideal, but I would prefer that over having them all in one file.

Snooty answered 20/5, 2020 at 15:26 Comment(2)
How do you make paths work with ts-loader?Watthour
This ended up working for my use case.Sottish
T
4

In my case, I had the following code:

export class CreateUser {
  @IsNotEmpty()
  userName: string
  @IsNotEmpty()
  photoURL: string
  @IsNotEmpty()
  displayName: string
  ...
  @IsDefined()
  @IsNotEmptyObject()
  @ValidateNested()
  @Type(() => CreateStreakConfig)
  streakConfig: CreateStreakConfig
}

export class CreateStreakConfig {
  @IsNotEmpty()
  minimumDistance: number
  @IsNotEmpty()
  unitOfDistanceType: typeof STREAK_CONFIG_UNIT_OF_DISTANCE_MILES | typeof STREAK_CONFIG_UNIT_OF_DISTANCE_KILOMETERS
}

I'm assuming during NestJs initialization, they instantiate each object in order. So simply switching them solved my issue:


export class CreateStreakConfig {
  @IsNotEmpty()
  minimumDistance: number
  @IsNotEmpty()
  unitOfDistanceType: typeof STREAK_CONFIG_UNIT_OF_DISTANCE_MILES | typeof STREAK_CONFIG_UNIT_OF_DISTANCE_KILOMETERS
}

export class CreateUser {
  @IsNotEmpty()
  userName: string
  @IsNotEmpty()
  photoURL: string
  @IsNotEmpty()
  displayName: string
  ...
  @IsDefined()
  @IsNotEmptyObject()
  @ValidateNested()
  @Type(() => CreateStreakConfig)
  streakConfig: CreateStreakConfig
}

Hope this helps anyone who comes across this issue!

Tips answered 4/8, 2022 at 13:57 Comment(0)
R
1

For me it was the entity used as type that was the problem so I used typeof to work around

@ManyToOne(() => EntityName, (ett) => ett.property)
val: typeof EntityName;
Rhombohedral answered 30/7 at 13:38 Comment(0)
G
0

If you use ESM in your TypeScript project, you should use the Relation wrapper type in relation properties to avoid circular dependency issues.

https://typeorm.io/#relations-in-esm-projects

Glaze answered 1/12, 2023 at 9:18 Comment(1)
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From ReviewFishbolt
S
-1

You should edit like this in the file nest-cli.json:

{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "deleteOutDir": true,
    "webpack": true
  }
}
Sphygmoid answered 26/8, 2023 at 9:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.