DEV Community

Cover image for How to create a custom many to many relationship in TypeORM
Miguel
Miguel

Posted on • Updated on

How to create a custom many to many relationship in TypeORM

Hi guys, how are you today? I hope you are fine!

Big Hero Hug


Preface

Many-to-many relationships are quite common when you're developing any kind of app, from food stores to large inventory.

Most of the time this relationship is not simple and you need to add more information in the pivo table (table generated from the relationship). We will learn how to make this custom relationship using typeORM.


Concepts

First of all let's review some concepts:

The purpose of typeORM is to support JavaScript features that help you develop any type of application that uses databases - from small applications with a few tables to large-scale enterprise applications with multiple databases.

The many-to-many relationship infers that A can have many B and B can have many A. In other words, both sides can report a variety on the other side.


To work

After all the initial configuration of typeORM, we need to generate the models and migrations.

In this example, we will build heroes that can have different individualities.

Midoriya,boku no hero

We can create models with the following command:



typeorm  entity:create -n Hero


Enter fullscreen mode Exit fullscreen mode

Our hero will have a name and skills. Put this in your model:




@Entity("heros")
class Hero {
  @PrimaryGeneratedColumn("increment")
  id: number;

  @Column({ length: 30 })
  name: string;

  @ManyToMany(() => Skill)
  @JoinTable({
    name: "hero_skills",
  })
  skills: Skill[];
}

export default Hero;



Enter fullscreen mode Exit fullscreen mode
src/models/Hero.ts

and...



typeorm  entity:create -n Skill


Enter fullscreen mode Exit fullscreen mode


@Entity("skills")
class Skill {
  @PrimaryGeneratedColumn("increment")
  id: number;

  @Column()
  skill: string;
}

export default Skill;


Enter fullscreen mode Exit fullscreen mode
src/models/Skill.ts

And here comes the magic, let's create a model to reference the relationship!



typeorm  entity:create -n HeroSkills


Enter fullscreen mode Exit fullscreen mode

And inside let's put:



@Entity("hero_skills")
class HeroSkills {

  @Column({ type: "text" })
  description: string;

  @Column({ type: "int" })
  episode: number;

  @PrimaryColumn({ type: "int" })
  skills_id: number;

  @PrimaryColumn({ type: "int" })
  heros_id: number;

  @OneToOne(() => Hero)
  @JoinTable()
  hero: Hero;

  @OneToOne(() => Skill)
  @JoinTable()
  skill: Skill;
}

export default HeroSkills;



Enter fullscreen mode Exit fullscreen mode
src/models/HeroSkills.ts

In this case, we'll show you when a hero first demonstrated his individuality and what he looks like.

Let's do migrations now



typeorm migration:create -n CreateHeroTable


Enter fullscreen mode Exit fullscreen mode


...
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.createTable(
      new Table({
        name: "heros",
        columns: [
          {
            name: "id",
            type: "int",
            isPrimary: true,
            generationStrategy: "increment",
          },
          {
            name: "name",
            type: "varchar",          
          },
          {
            name: "skills_id",
            type: "int",          
          },
        ],
        foreignKeys: [
          {
            name: "Skills",
            referencedTableName: "skills",
            referencedColumnNames: ["id"],
            columnNames: ["skills_id"],
            onDelete: "CASCADE",
            onUpdate: "CASCADE",
          },
        ],
      })
    );
  }
...


Enter fullscreen mode Exit fullscreen mode
src/database/migrations/

Note that the foreign key relationship is explicit.

*In the case of a many-to-many relationship, it does not matter in which table the relationship will be explicit, as long as the relationship exists



....
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.createTable(
      new Table({
        name: "skills",
        columns: [
          {
            name: "id",
            type: "int",
            isPrimary: true,
            generationStrategy: "increment",
          },
          {
            name: "skill",
            type: "varchar",
          },
        ],
      })
    );
  }
...


Enter fullscreen mode Exit fullscreen mode
src/database/migrations/

Now let's go to the relational migration:



typeorm migration:create -n CreateHeroSkillsTable


Enter fullscreen mode Exit fullscreen mode


...
 await queryRunner.createTable(
      new Table({
        name: "hero_skills",
        columns: [
          {
            name: "id",
            type: "int",
            isPrimary: true,
            generationStrategy: "increment",
          },
          {
            name: "description",
            type: "text",
          },
          {
            name: "episode",
            type: "int",
          },
          {
            name: "heros_id",
            type: "int",
          },
          {
            name: "skills_id",
            type: "int",
          },
        ],
        foreignKeys: [
          {
            name: "Hero",
            referencedTableName: "heros",
            referencedColumnNames: ["id"],
            columnNames: ["heros_id"],
            onDelete: "CASCADE",
            onUpdate: "CASCADE",
          },
          {
            name: "Skill",
            referencedTableName: "skills",
            referencedColumnNames: ["id"],
            columnNames: ["skills_id"],
            onDelete: "CASCADE",
            onUpdate: "CASCADE",
          },
        ],
      })
    );
...


Enter fullscreen mode Exit fullscreen mode

And finally run:



typeorm migration:run


Enter fullscreen mode Exit fullscreen mode

If all goes well, all tables will be created with their respective relationships!

All might congrats

Time is very important, thanks for sharing a little bit of yours with me 😊.

image

image

Top comments (7)

Collapse
 
gondar00 profile image
Gandharv

CreateHeroSkillsTable the name is "challenge_words" instead it should be "hero_skills" ?

Collapse
 
crazyoptimist profile image
crazyoptimist

In the HeroSkills class, isn't it JoinColumn instead of JoinTable?

Collapse
 
kanelv profile image
Cuong Le

And why the relation is @OneToOne instead of @ManyToOne?

Collapse
 
kanelv profile image
Cuong Le

Could any body help me?
Why @OneToOne(() => Hero) and @OneToOne(() => Skill) on the HeroSkills?
I think it should be @ManyToOne(() => Hero) and @ManyToOne(() => Skill)
Many thank!

Collapse
 
crazyoptimist profile image
crazyoptimist

Perfect. Thanks!

Collapse
 
kamanssafk profile image
Kamanss-afk

Hi, what is the best way to add data to such a structure? Need to make two requests? The first, to fill in the "heros" table, the second, to fill in the "HeroSkills" table?

Collapse
 
kanelv profile image
Cuong Le

sure, you should do like this to make it clear.
Actually, I think you should consider to use transaction to make sure that everything ok, if something is wrong, everything before that will be rolled back.