Many-to-many relations

Many-to-many is a relation where A contains multiple instances of B, and B contain multiple instances of A.Let’s take for example Question and Category entities.Question can have multiple categories, and each category can have multiple questions.

  1. import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
  2. @Entity()
  3. export class Category {
  4. @PrimaryGeneratedColumn()
  5. id: number;
  6. @Column()
  7. name: string;
  8. }
  1. import {Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable} from "typeorm";
  2. import {Category} from "./Category";
  3. @Entity()
  4. export class Question {
  5. @PrimaryGeneratedColumn()
  6. id: number;
  7. @Column()
  8. title: string;
  9. @Column()
  10. text: string;
  11. @ManyToMany(type => Category)
  12. @JoinTable()
  13. categories: Category[];
  14. }

@JoinTable() is required for @ManyToMany relations.You must put @JoinTable on one (owning) side of relation.

This example will produce following tables:

  1. +-------------+--------------+----------------------------+
  2. | category |
  3. +-------------+--------------+----------------------------+
  4. | id | int(11) | PRIMARY KEY AUTO_INCREMENT |
  5. | name | varchar(255) | |
  6. +-------------+--------------+----------------------------+
  7. +-------------+--------------+----------------------------+
  8. | question |
  9. +-------------+--------------+----------------------------+
  10. | id | int(11) | PRIMARY KEY AUTO_INCREMENT |
  11. | title | varchar(255) | |
  12. +-------------+--------------+----------------------------+
  13. +-------------+--------------+----------------------------+
  14. | question_categories_category |
  15. +-------------+--------------+----------------------------+
  16. | questionId | int(11) | PRIMARY KEY FOREIGN KEY |
  17. | categoryId | int(11) | PRIMARY KEY FOREIGN KEY |
  18. +-------------+--------------+----------------------------+

Example how to save such relation:

  1. const category1 = new Category();
  2. category1.name = "animals";
  3. await connection.manager.save(category1);
  4. const category2 = new Category();
  5. category2.name = "zoo";
  6. await connection.manager.save(category2);
  7. const question = new Question();
  8. question.title = "dogs";
  9. question.text = "who let the dogs out?";
  10. question.categories = [category1, category2];
  11. await connection.manager.save(question);

With cascades enabled you can save this relation with only one save call.

To load question with categories inside you must specify relation in FindOptions:

  1. const questionRepository = connection.getRepository(Question);
  2. const questions = await questionRepository.find({ relations: ["categories"] });

Or using QueryBuilder you can join them:

  1. const questions = await connection
  2. .getRepository(Question)
  3. .createQueryBuilder("question")
  4. .leftJoinAndSelect("question.categories", "category")
  5. .getMany();

With eager loading enabled on a relation you don’t have to specify relation or join it - it will ALWAYS be loaded automatically.

Relations can be uni-directional and bi-directional.Uni-directional are relations with a relation decorator only on one side.Bi-directional are relations with decorators on both sides of a relation.

We just created a uni-directional relation. Let’s make it bi-directional:

  1. import {Entity, PrimaryGeneratedColumn, Column, ManyToMany} from "typeorm";
  2. import {Question} from "./Question";
  3. @Entity()
  4. export class Category {
  5. @PrimaryGeneratedColumn()
  6. id: number;
  7. @Column()
  8. name: string;
  9. @ManyToMany(type => Question, question => question.categories)
  10. questions: Question[];
  11. }
  1. import {Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable} from "typeorm";
  2. import {Category} from "./Category";
  3. @Entity()
  4. export class Question {
  5. @PrimaryGeneratedColumn()
  6. id: number;
  7. @Column()
  8. title: string;
  9. @Column()
  10. text: string;
  11. @ManyToMany(type => Category, category => category.questions)
  12. @JoinTable()
  13. categories: Category[];
  14. }

We just made our relation bi-directional. Note, the inverse relation does not have a @JoinTable.@JoinTable must be only on one side of the relation.

Bi-directional relations allow you to join relations from both sides using QueryBuilder:

  1. const categoriesWithQuestions = await connection
  2. .getRepository(Category)
  3. .createQueryBuilder("category")
  4. .leftJoinAndSelect("category.questions", "question")
  5. .getMany();

many-to-many relations with custom properties

In case you need to have additional properties to your many-to-many relationship you have to create a new entity yourself.For example if you would like entities Post and Category to have a many-to-many relationship with a createdAt propertyassociated to it you have to create entity PostToCategory like the following:

  1. import { Entity, Column, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
  2. import { Post } from "./post";
  3. import { Category } from "./category";
  4. @Entity()
  5. export class PostToCategory {
  6. @PrimaryGeneratedColumn()
  7. public postToCategoryId!: number;
  8. @Column()
  9. public postId!: number;
  10. @Column()
  11. public categoryId!: number;
  12. @Column()
  13. public order!: number;
  14. @ManyToOne(type => Post, post => post.postToCategories)
  15. public post!: Post;
  16. @ManyToOne(type => Category, category => category.postToCategories)
  17. public category!: Category;
  18. }

Additionally you will have to add a relationship like the following to Post and Category:

  1. // category.ts
  2. ...
  3. @OneToMany((type) => PostToCategory, (postToCategory) => postToCategory.category)
  4. public postToCategories!: PostToCategory[];
  5. // post.ts
  6. ...
  7. @OneToMany((type) => PostToCategory, (postToCategory) => postToCategory.post)
  8. public postToCategories!: PostToCategory[];