How to create a custom many to many relationship in TypeORM
Miguel
Posted on August 3, 2021
Hi guys, how are you today? I hope you are fine!
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.
We can create models with the following command:
typeorm entity:create -n Hero
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;
and...
typeorm entity:create -n Skill
@Entity("skills")
class Skill {
@PrimaryGeneratedColumn("increment")
id: number;
@Column()
skill: string;
}
export default Skill;
And here comes the magic, let's create a model to reference the relationship!
typeorm entity:create -n HeroSkills
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;
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
...
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",
},
],
})
);
}
...
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",
},
],
})
);
}
...
Now let's go to the relational migration:
typeorm migration:create -n CreateHeroSkillsTable
...
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",
},
],
})
);
...
And finally run:
typeorm migration:run
If all goes well, all tables will be created with their respective relationships!
Posted on August 3, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.