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.
0 Comments