Search code examples
javascriptmysqlnode.jsexpresstypeorm

Save relation in @ManyToMany in typeORM overwrites previous entry


I have made a follower system using typeorm/mysql and nodejs/express.

Save logic:

router.post("/subscriptions", async (req, res) => {
  const currentUserId = Number(req.headers.userid);
  const userToFollowId = req.body.userToFollowId;

  const userRepository = getRepository(User);

  try {
    let user;
    let userToFollow;

    user = await userRepository.findOneOrFail(currentUserId);
    userToFollow = await userRepository.findOneOrFail(userToFollowId);

    if (user && userToFollow) {
      user.followers = [...(user?.followers || []), userToFollow];
      await userRepository.save(user);
      res.status(201).send("Followed");
    }
  } catch (e) {
    console.log(e);
  }
});

@User Entity which has self referencing relation:

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id!: number;

  @Column({ nullable: true })
  name!: string;

  @Column()
  email!: string;

  @Column()
  password!: string;

  @OneToMany(() => Post, (post) => post.user)
  posts!: Post[];

  @OneToMany(() => Comment, (comment) => comment.user)
  comments!: Comment[];

  @ManyToMany(() => User, (user) => user.following, { cascade: true })
  @JoinTable()
  followers!: User[];

  @ManyToMany(() => User, (user) => user.followers)
  following!: User[];

  @Column({
    default: () => "NOW()",
  })
  createdAt!: Date;

  @Column({
    default: () => "NOW()",
  })
  @UpdateDateColumn()
  updatedAt!: Date;
}

Let's say curentUserId is 5 and userToFollow ID is 6. It will be saved to the database successfully. The problem occurs when I try to save another entry by using same currentUserIdof 5 and userToFollow some other ID (for example 7), in this case previous entry in database: User 5 follows user with the ID of 6 will be overwritten to: User 5 follows user with the ID of 7

Here is the query when I save it for the first time:

query: SELECT `User`.`id` AS `User_id`, `User`.`name` AS `User_name`, `User`.`email` AS `User_email`, `User`.`password` AS `User_password`, `User`.`createdAt` AS `User_createdAt`, `User`.`updatedAt` AS `User_updatedAt` FROM `user` `User` WHERE `User`.`id` IN (?) -- PARAMETERS: [5]
query: SELECT `User`.`id` AS `User_id`, `User`.`name` AS `User_name`, `User`.`email` AS `User_email`, `User`.`password` AS `User_password`, `User`.`createdAt` AS `User_createdAt`, `User`.`updatedAt` AS `User_updatedAt` FROM `user` `User` WHERE `User`.`id` IN (?) -- PARAMETERS: ["6"]
query: SELECT `User`.`id` AS `User_id`, `User`.`name` AS `User_name`, `User`.`email` AS `User_email`, `User`.`password` AS `User_password`, `User`.`createdAt` AS `User_createdAt`, `User`.`updatedAt` AS `User_updatedAt` FROM `user` `User` WHERE `User`.`id` IN (?, ?) -- PARAMETERS: [5,6]
query: SELECT `User_followers_rid`.`userId_1` AS `userId_1`, `User_followers_rid`.`userId_2` AS `userId_2` FROM `user` `user` INNER JOIN `user_followers_user` `User_followers_rid` ON (`User_followers_rid`.`userId_1` = ? AND `User_followers_rid`.`userId_2` = `user`.`id`) OR (`User_followers_rid`.`userId_1` = ? AND `User_followers_rid`.`userId_2` = `user`.`id`)  ORDER BY `User_followers_rid`.`userId_2` ASC, `User_followers_rid`.`userId_1` ASC -- PARAMETERS: [5,6]
query: START TRANSACTION
query: INSERT INTO `user_followers_user`(`userId_1`, `userId_2`) VALUES (?, ?) -- PARAMETERS: [5,6]
query: COMMIT

And this is second one where I think the problem occurs (please note the DELETE part):

query: START TRANSACTION
query: INSERT INTO `user_followers_user`(`userId_1`, `userId_2`) VALUES (?, ?) -- PARAMETERS: [5,6]
query: COMMIT
query: SELECT `User`.`id` AS `User_id`, `User`.`name` AS `User_name`, `User`.`email` AS `User_email`, `User`.`password` AS `User_password`, `User`.`createdAt` AS `User_createdAt`, `User`.`updatedAt` AS `User_updatedAt` FROM `user` `User` WHERE `User`.`id` IN (?) -- PARAMETERS: [5]
query: SELECT `User`.`id` AS `User_id`, `User`.`name` AS `User_name`, `User`.`email` AS `User_email`, `User`.`password` AS `User_password`, `User`.`createdAt` AS `User_createdAt`, `User`.`updatedAt` AS `User_updatedAt` FROM `user` `User` WHERE `User`.`id` IN (?) -- PARAMETERS: ["7"]
query: SELECT `User`.`id` AS `User_id`, `User`.`name` AS `User_name`, `User`.`email` AS `User_email`, `User`.`password` AS `User_password`, `User`.`createdAt` AS `User_createdAt`, `User`.`updatedAt` AS `User_updatedAt` FROM `user` `User` WHERE `User`.`id` IN (?, ?) -- PARAMETERS: [5,7]
query: SELECT `User_followers_rid`.`userId_1` AS `userId_1`, `User_followers_rid`.`userId_2` AS `userId_2` FROM `user` `user` INNER JOIN `user_followers_user` `User_followers_rid` ON (`User_followers_rid`.`userId_1` = ? AND `User_followers_rid`.`userId_2` = `user`.`id`) OR (`User_followers_rid`.`userId_1` = ? AND `User_followers_rid`.`userId_2` = `user`.`id`)  ORDER BY `User_followers_rid`.`userId_2` ASC, `User_followers_rid`.`userId_1` ASC -- PARAMETERS: [5,7]
query: START TRANSACTION
query: INSERT INTO `user_followers_user`(`userId_1`, `userId_2`) VALUES (?, ?) -- PARAMETERS: [5,7]
query: DELETE FROM `user_followers_user` WHERE `userId_1` = ? AND `userId_2` = ? -- PARAMETERS: [5,6]
query: COMMIT

Solution

  • The reason you have this problem is that when you query user, you are not adding followers relation with it. Write a console.log(user) and you will see user object doesn't have followers attribute. That's why every time you call the api, it replaces the old one with new one.

    You have to add followers relation in your query. This will return all the existing follower's list in form of user.followers of User object. Another thing you can use user.followers.push(userToFollow); instead of user.followers = [...(user?.followers || []), userToFollow]; in your code. Use the code below,

    try {
                let user = await userRepository.findOneOrFail(currentUserId, { relations: ["followers"] });
                let userToFollow = await userRepository.findOneOrFail(userToFollowId);
    
                if (user && userToFollow) {
                    user.followers.push(userToFollow);
                    await userRepository.save(user);
                    return res.status(201).send("Followed");
                } else {
                    throw new Error("User not found!")
                }
            } catch (e) {
                console.log(e);
            }