I have just started using TypeORM and I'm struggling getting the following relationship to work:
User->Friends, whereas a Friend is also a User Object. My getters, getFriends & getFriendsInverse are working, however; I do now want to distinguish between the two. In other words; when I perform a mysql join I do not want to do a left join on friends and another one on inverseFriends.
The getter getFriends() needs to return all friends, regardless of which "side" the object I'm on.
Does that make sense?
This is my model definition:
getFriends() {
// This method should also return inverseFriends;
// I do not want to return the concat version; this should
// happen on the database/orm level
// So I dont want: this.friends.concat(this.inverseFriends)
return this.friends;
}
@ManyToMany(type => User, user => user.friendsInverse, {
cascadeInsert: false,
cascadeUpdate: false,
})
@JoinTable()
friends = [];
@ManyToMany(type => User, user => user.friends, {
cascadeInsert: true,
cascadeUpdate: true,
cascadeRemove: false,
})
friendsInverse = [];
I hope someone understands my question :D Thanks Matt
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
).
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)
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With