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!
Top comments (7)
CreateHeroSkillsTable the name is "challenge_words" instead it should be "hero_skills" ?
In the
HeroSkills
class, isn't itJoinColumn
instead ofJoinTable
?And why the relation is @OneToOne instead of @ManyToOne?
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!
Perfect. Thanks!
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?
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.