Search code examples
mysqlnode.jsormtypeorm

Self-Referencing ManyToMany Relationship TypeORM


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


Solution

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