Self-Referencing ManyToMany Relationship TypeORM

You can self-reference your relations. Here is an example of a simple directed graph (aka a node can have a parent and multiple children).

@Entity()
export class Service extends BaseEntity {

  @PrimaryGeneratedColumn()
  id: number;
  
  @Column()
  @Index({ unique: true })
  title: string;

  @ManyToOne(type => Service, service => service.children)
  parent: Service;

  @OneToMany(type => Service, service => service.parent)
  children: Service[];
}

An important note to keep in mind is that these relations are not auto loaded when reading an object from the DB with find* functions.

To actually load them, you have to use query builder at the moment and join them. (You can join multiple levels.) An example:

let allServices = await this.repository.createQueryBuilder('category')
  .andWhere('category.price IS NULL')
  .innerJoinAndSelect('category.children', 'product')
  .leftJoinAndSelect('product.children', 'addon')
  .getMany();

Please note how I used different names to reference them (category, product, and addon).


2021 here, was searching for the same problem and find a way to solve it without custom raw SQL (providing same model as example for simplicity):

import { Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn, JoinTable } from 'typeorm';

@Entity(UserModel.MODEL_NAME)
export class UserModel {
  static MODEL_NAME = 'users';

  @PrimaryGeneratedColumn()
  id?: number;

  @Column({ type: 'varchar', unique: true, length: 50 })
  username: string;

  @Column({ type: 'varchar', length: 50, unique: true })
  email: string;

  @ManyToMany(type => UserModel)
  @JoinTable({ joinColumn: { name: 'users_id_1' } })
  friends: UserModel[];

  @Column({ type: 'varchar', length: 300 })
  password: string;
}

The key moment here is to set joinColumn for JoinTable.

When you are defining ManyToMany relationship, TypeORM automatically creates n-n table users_friends_users with one column named user_id_1 and another user_id_2 (they are automatically incremented if foreign key is the same)

So it is enough to choose any column from this table as "primary join column" and it works


I believe I'm 3 years late, but better late than ever. The most upvoted answer does not answer the question, as it only works for tree-like and hierarchical structures, so if you follow that example, this would happen:

               Fred
               /  \
          Albert  Laura
          /   \
       John   Foo

In this example, Foo can't be friends with Fred, because he can only have one parent. Friends is not a tree structure, it's like a net. The answer would be the following:

import { Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';

@Entity(UserModel.MODEL_NAME)
export class UserModel {
  static MODEL_NAME = 'users';

  @PrimaryGeneratedColumn()
  id?: number;

  @Column({ type: 'varchar', unique: true, length: 50 })
  username: string;

  @Column({ type: 'varchar', length: 50, unique: true })
  email: string;

  @ManyToMany(type => UserModel)
  @JoinTable()
  friends: UserModel[];

  @Column({ type: 'varchar', length: 300 })
  password: string;
}

This would create a table where relations between people would be saved. Now for the next important stuff. How do you query this and get a user's friends? It's not as easy as it seems, I've played hours with this and haven't been able to do it with TypeORM methods or even query builder. The answer is: Raw Query. This would return an array with the user's friends:

async findFriends(id: Id): Promise<UserModel[]> {
    return await this.userORM.query(
      ` SELECT * 
        FROM users U
        WHERE U.id <> $1
          AND EXISTS(
            SELECT 1
            FROM users_friends_users F
            WHERE (F."usersId_1" = $1 AND F."usersId_2" = U.id )
            OR (F."usersId_2" = $1 AND F."usersId_1" = U.id )
            );  `,
      [id],
    );
  }

(users_friends_users is the autogenerated name that typeORM gives to the table where the relations between users are saved)