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)