DEV Community

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

How to create a custom many to many relationship in TypeORM

Miguel
you’re the biggest tool.
・3 min read

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: "challenge_words",
        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

Discussion (0)