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.

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 *