Avoiding duplication is a fundamental goal of any developer. The same goes for designing entities. By using TypeORM Entity Inheritance, we can make sure that our entity definitions in TypeORM don’t suffer from duplication.
To implement TypeORM Entity Inheritance, we can use various approaches such as concrete table inheritance, single table inheritance and also, embedded entities to some extent. Depending on the requirement, we can choose any of the three options to reduce repetitive code.
Let us look at each option one-by-one.
1 – TypeORM Entity Inheritance using Concrete Table
The first pattern to implement entity inheritance is by using a concrete table.
For example, consider the below TypeORM entity definition.
import { Column, PrimaryGeneratedColumn } from "typeorm";
export abstract class Person {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
}
This is a common class to denote an individual. It has three fields id
, firstName
and lastName
. We also declare it as abstract
.
Now, suppose we are dealing with passengers in a flight. We want to create a specific class to store passenger information. It can use the fields from the Person
class as every passenger will have a name.
See below example:
import { Column, Entity } from "typeorm";
import { Person } from "./Person";
export enum MealPreference {
VEGETARIAN = "vegetarian",
NONVEGETARIAN = "non-vegetarian",
}
@Entity()
export class Passenger extends Person {
@Column()
age: number;
@Column({
type: "enum",
enum: MealPreference,
default: MealPreference.VEGETARIAN
})
mealPreference: MealPreference;
}
As you can see, Passenger
class extends Person
. This makes sure that apart from the fields specific to the Passenger
class, it also inherits the fields from the Person
class.
We can simply use the Passenger
entity to insert records into the corresponding table.
const passenger = new Passenger();
passenger.firstName = "Adam";
passenger.lastName = "Smith";
passenger.age = 35;
passenger.mealPreference = MealPreference.NONVEGETARIAN;
const passengerRepository = AppDataSource.getRepository(Passenger);
await passengerRepository.save(passenger);
The fields firstName
, lastName
are directly available on the Passenger
object. Using the TypeORM Datasource, we obtain an instance of PassengerRepository
and save the record into database.
Basically, only one concrete table will be created in this case i.e. the passenger
table.
2 – TypeORM Entity Inheritance using Single Table
TypeORM also supports single table inheritance.
Basically, in this approach, multiple classes can inherit from a single parent class with their own specific properties. However, in the database, they are all stored in a single table.
See below example:
import { Column, Entity, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
@Entity()
@TableInheritance({ column: { type: "varchar", name: "type" } })
export class Person {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
}
Check out the @TableInheritance
decorator. Now, we simply make the Passenger entity as a child entity.
import { ChildEntity, Column, Entity } from "typeorm";
import { Person } from "./Person";
export enum MealPreference {
VEGETARIAN = "vegetarian",
NONVEGETARIAN = "non-vegetarian",
}
@ChildEntity()
export class Passenger extends Person {
@Column()
age: number;
@Column({
type: "enum",
enum: MealPreference,
default: MealPreference.VEGETARIAN
})
mealPreference: MealPreference;
}
Here, we use the @ChildEntity()
decorator and also extend the Person
class.
While Single Table Inheritance approach also works, I don’t prefer to use it.
3 – Embedded Entities in TypeORM
While inheritance does the job of avoiding duplication, it can also lead to issues in case the hierarchy grows. Sometimes, composition is better than inheritance.
TypeORM supports composition by means of embedded entities.
For example, consider an entity to store the passenger’s seat details.
import { Column } from "typeorm";
export enum SeatColumn {
A = "A",
B = "B",
C = "C",
D = "D",
E = "E",
F = "F"
}
export class Seat {
@Column({
type: "enum",
enum: SeatColumn
})
column: SeatColumn;
@Column()
row: number;
}
Basically, this is a normal class with two fields – column
and row
.
We can embed this class in the Passenger
entity.
import { Column, Entity } from "typeorm";
import { Person } from "./Person";
import { Seat } from "./Seat";
export enum MealPreference {
VEGETARIAN = "vegetarian",
NONVEGETARIAN = "non-vegetarian",
}
@Entity()
export class Passenger extends Person {
@Column()
age: number;
@Column({
type: "enum",
enum: MealPreference,
default: MealPreference.VEGETARIAN
})
mealPreference: MealPreference;
@Column(() => Seat)
seat: Seat;
}
In the @Column
decorator, we indicate that the seat
field derives from the Seat
class. In other words, we have embedded Seat
class into the Passenger
class rather than duplicating the seat information.
Below is how you can interact with embedded entities to save data.
const passenger = new Passenger();
passenger.firstName = "Adam";
passenger.lastName = "Smith";
passenger.age = 35;
passenger.mealPreference = MealPreference.NONVEGETARIAN;
const seat = new Seat();
seat.column = SeatColumn.B;
seat.row = 9
passenger.seat = seat;
const passengerRepository = AppDataSource.getRepository(Passenger);
await passengerRepository.save(passenger);
Conclusion
As we saw in previous sections, all of these options enable us to implement entity inheritance in TypeORM. Whichever option we choose, ultimately it results in better TypeORM entity definitions.
If you have any comments or queries about this post, please mention them in the comments section below.
0 Comments