In any reasonably realistic data model, there are bound to be relations between tables or entities. ORMs also provide way to developers to describe these entity relations in a programmatic approach. TypeORM Entity Relations is not different. Using entity relations in TypeORM, we can describe various combinations such as One-to-One, One-to-Many, Many-to-Many relationships.

In this post, we will look at how to describe these relationships and use them in your application logic.

1 – TypeORM One-to-One Entity Relation

In one-to-one relation, entity A contains only one instance of entity B and entity B contains only one instance of entity A.

Consider that we are building a small data model for a Twitter-like application. We have a table to store basic user information. And then, we have something like a user profile table. We can describe these tables as TypeORM Entities.

First, we have the Member entity.

import { Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from "typeorm"
import { Profile } from "./Profile";

@Entity()
export class Member {

    @PrimaryGeneratedColumn()
    id: number

    @Column()
    firstName: string

    @Column()
    lastName: string

    @OneToOne(() => Profile, {cascade: true})
    @JoinColumn()
    profile: Profile;
}

Then, we have the Profile entity.

import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class Profile {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    followers: number;

    @Column()
    following: number;
} 

The @OneToOne() decorator describes the entity relationship and the target relation. In this case, the Member entity is related to the Profile entity. The @JoinColumn() is a mandatory decorator for @OneToOne() decorator and must be set on only one side of the relation. In this case, the Member table will have an actual column to contain the relation id to the Profile table.

Also, to insert data in the Member table and User table, we need to create Member and Profile instances and save the Member instance.

const member = new Member();

member.firstName = "Adam";
member.lastName = "Smith";

const profile = new Profile();
profile.followers = 100;
profile.following = 150;

member.profile = profile;

await memberRepository.save(member);

The cascade: true option ensures that we only have to save the Member instance. If the cascade is set of false, we need to save both Member and Profile separately.

2 – TypeORM One-to-Many Relation

Let us now look at One-To-Many Entity Relation in TypeORM. For our example, a member can publish one or more than one tweet. However, one tweet belongs to only one member. Hence, Member and Tweet have a One-to-Many relationship.

Below is the updated Member entity.

import { Entity, PrimaryGeneratedColumn, Column, OneToMany, OneToOne, JoinColumn } from "typeorm"
import { Tweet } from "./Tweet"
import { Profile } from "./Profile";

@Entity()
export class Member {

    @PrimaryGeneratedColumn()
    id: number

    @Column()
    firstName: string

    @Column()
    lastName: string

    @OneToOne(() => Profile, {cascade: true})
    @JoinColumn()
    profile: Profile;

    @OneToMany(() => Tweet, (tweet) => tweet.member, {cascade: true})
    tweets: Tweet[]
}

Next, we have the Tweet entity.

import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import { Member } from "./Member";

@Entity()
export class Tweet {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    content: string;

    @ManyToOne(() => Member, (member) => member.tweets)
    member: Member;
}

The @OneToMany decorator is added to the tweets column. The target for this column is the Tweet entity. The @JoinColumn decorator is not needed in the case of @OneToMany(). The corresponding @ManyToOne() decorator is present in the Tweet entity. The target in the Tweet entity is the Member entity.

We can save the entity data for the above combination as below:

const member = new Member();

member.firstName = "Adam";
member.lastName = "Smith";

const profile = new Profile();
profile.followers = 100;
profile.following = 150;

member.profile = profile;

const tweet = new Tweet();
tweet.content = "Programming in Typescript is fun!";

member.tweets = [tweet]

await memberRepository.save(member);

3 – TypeORM Many-to-Many Entity Relation

Lastly, we also need to consider the Many-to-Many relation in TypeORM.

A typical case of our example would be a Tweet having several Hashtags. Also, one Hashtag having several tweets.

How do we model this relationship? In this case, we need to use Many-to-Many relation.

Below is the updated Tweet entity.

import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import { Hashtag } from "./Hashtag";
import { Member } from "./Member";

@Entity()
export class Tweet {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    content: string;

    @ManyToOne(() => Member, (member) => member.tweets)
    member: Member;

    @ManyToMany((type) => Hashtag, (hashtag) => hashtag.tweets, {
        cascade: true
    })
    @JoinTable()
    hashtags: Hashtag[]
}

Then, we have the Hashtag entity.

import { Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn } from "typeorm";
import { Tweet } from "./Tweet";

@Entity()
export class Hashtag {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    tag: string;

    @ManyToMany((type) => Tweet, (tweet) => tweet.hashtags)
    tweets: Tweet[]
}

Here, @ManyToMany() decorator describes the target entity for the relation. Also, we need to use @JoinTable() decorator. This decorator should be on the side that basically owns the relation. In this case, the Tweet entity owns the relation.

With cascade set to true, we can save data with this relation using only one call to the DB. See below example.

const programming = new Hashtag();
programming.tag = "Programming"

const typescript = new Hashtag();
typescript.tag = "Typescript"

const tweet = new Tweet();
tweet.content = "Programming in Typescript is fun!";
tweet.hashtags = [programming, typescript];

member.tweets = [tweet]

await tweetRepository.save(tweet);

4 – TypeORM Cascade Options

The TypeORM Cascade options allows us to build cascading relationships between entities.

For example, cascade: ["insert"] will delete data from the relation table but from the Hashtag table when we remove the Tweet record. See below example:

@ManyToMany((type) => Hashtag, (hashtag) => hashtag.tweets, {
        cascade: ["insert"]
})
@JoinTable()
hashtags: Hashtag[]

On the other hand, cascade: ["remove"] will delete data from the relation table but also from the Hashtag table when we remove the Tweet record.

@ManyToMany((type) => Hashtag, (hashtag) => hashtag.tweets, {
        cascade: ["remove"]
})
@JoinTable()
hashtags: Hashtag[]

It is important to properly test the cascading relationship. Otherwise, it can lead to unpredictable results.

Conclusion

With this, we have successfully looked at the various types of TypeORM Entity Relations with code examples.

Want to learn more about TypeORM. Check out this post on TypeORM Entity Inheritance.

If you have any comments or queries about this post, please feel free to mention them in the comments section below.

Categories: BlogTypeORM

Saurabh Dashora

Saurabh is a Software Architect with over 12 years of experience. He has worked on large-scale distributed systems across various domains and organizations. He is also a passionate Technical Writer and loves sharing knowledge in the community.

0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *