Single Table Inheritance with TypeORM and PostgreSQL

Introduction

Single Table Inheritance is a design pattern that allows you to represent a hierarchy of objects using a single database table. Each subclass in the hierarchy is represented as a row in the table, with a type field that identifies the class to which the row belongs. Single Table Inheritance can simplify your database schema, improve performance, and make it easier to work with complex object hierarchies.

TypeORM is an Object-Relational Mapping (ORM) library for TypeScript and JavaScript that can be used to map classes to database tables. TypeORM supports Single Table Inheritance out of the box, making it easy to implement this design pattern in your TypeScript or JavaScript applications.

In this blog post, we'll show you how to use TypeORM and PostgreSQL to implement Single Table Inheritance in your application.

Setting up the Project

Before we can begin implementing Single Table Inheritance, we need to set up our project with TypeORM and PostgreSQL. Here's how to do it:

  1. Install TypeORM and its dependencies using npm or yarn:
npm install typeorm reflect-metadata pg
  1. Create a new TypeScript file to define our entities. We'll call this file entities.ts.

  2. Create a new PostgreSQL database to store our data.

  3. Configure TypeORM to connect to the database. You can do this by creating a config.ts file in the root of your project with the following contents:

import { DataSource } from 'typeorm';
import { Car } from './entities';

export default {
  type: 'postgres',
  host: 'localhost',
  port: 5432,
  username: 'postgres',
  password: 'password',
  database: 'mydatabase',
  entities: [
    Car,
  ],
  synchronize: true,
  logging: false,
  dropSchema: false,
  migrationsRun: true,
  migrations: [],
  subscribers: [],
  cli: {
    entitiesDir: 'src/entities',
    migrationsDir: 'src/migrations',
    subscribersDir: 'src/subscribers',
  },
  dataSourceClass: DataSource,
};

Replace <username>, <password>, and <database> with your PostgreSQL credentials.

  1. Add a script to your package.json file to run the TypeScript compiler and start the application:
{
  "scripts": {
    "start": "tsc && node index.js"
  }
}

Defining the Entity Classes

Now that we have our project set up, we can define our entity classes. We'll define a base class called Vehicle that will represent the common fields shared by all vehicles. We'll also define two subclasses, Car and Bike, which will inherit from Vehicle and add additional fields specific to those types of vehicles.

Here's how to define the entity classes:

import { Entity, Column, ChildEntity } from 'typeorm';

@Entity()
export class Vehicle {
  @Column()
  id: number;

  @Column()
  brand: string;

  @Column()
  model: string;

  @Column()
  year: number;
}

@ChildEntity()
export class Car extends Vehicle {
  @Column()
  engine: string;
}

@ChildEntity()
export class Bike extends Vehicle {
  @Column()
  gears: number;
}

We first import the Entity, Column, and ChildEntity decorators from TypeORM. We then define a base class called Vehicle with four fields: id, brand, model, and year.

We then define two subclasses, Car and Bike, using the ChildEntity decorator. This tells TypeORM that these classes should be stored in the same table as Vehicle and that they should inherit the fields defined in Vehicle.

We add a field specific to each subclass: engine for Car and gears for Bike.

With our entity classes defined, we can now use TypeORM to map these classes to the database schema and start using Single Table Inheritance in our application.

Saving and Retrieving Entities

Now that we have our entity classes defined, we can use TypeORM to save and retrieve instances of these entities. Let's see how to do that.

Saving Entities

To save an entity, we create a new instance of the entity class and set its fields, then call the save() method on the TypeORM repository for that entity. Here's an example:

import { createConnection } from 'typeorm';
import { config } from './config';
import { Car } from './entities';

createConnection(config).then(async (connection) => {
  const carRepository = await connection.getRepository(Car);
  const car = new Car();
  car.brand = 'Tesla';
  car.model = 'Model S';
  car.year = 2022;
  car.engine = 'electric';

  await carRepository.save(car);
});

We first import the createConnection function from TypeORM and the Car the class we defined earlier.

We then create a new instance of Car, set its fields, and call the save() method on the carRepository repository.

Retrieving Entities

To retrieve entities, we can use the find() method on the TypeORM repository for that entity. Here's an example:

import { createConnection } from 'typeorm';
import { config } from './config';
import { Car, Bike } from './entities';

createConnection(config).then(async (connection) => {
  const carRepository = await connection.getRepository(Car);
  const bikeRepository = await connection.getRepository(Bike);

  const cars = await carRepository.find();
  const bikes = await bikeRepository.find();
});

We first import the createConnection function from TypeORM and the Car and Bike classes we defined earlier.

We then call the find() method on the carRepository and bikeRepository repositories to retrieve all instances of Car and Bike, respectively.

The resulting cars and bikes arrays will contain instances of Car and Bike with all their fields, including the ones inherited from Vehicle.

With these basic operations, we can use Single Table Inheritance in our application to store and retrieve different types of entities in the same table.

Querying the Database

In addition to saving and retrieving entities, we can also query the database for entities that match certain criteria. TypeORM provides a rich query builder API that makes it easy to build complex queries.

For example, let's say we want to retrieve all Vehicle entities with a year greater than 2020. We can do that using the query builder like this:

import { getRepository } from 'typeorm';
import {  } from './entities';
import { createConnection } from 'typeorm';
import { config } from './config';
import { Vehicle } from './entities';

createConnection(config).then(async (connection) => {
 const vehicleRepository = await connection.getRepository(Vehicle);

 const vehicles = await vehicleRepository.createQueryBuilder('vehicle')
  .where('vehicle.year > :year', { year: 2020 })
  .getMany();
});

We first import the createConnection function from TypeORM and the Vehicle class we defined earlier.

We then create a query builder instance by calling the createQueryBuilder() method on the vehicleRepository.

We use the where() method to specify the condition for the query, which is that the year the field is greater than 2020.

We finally call the getMany() method to execute the query and retrieve the resulting vehicles array.

This is just a simple example, but TypeORM's query builder API is capable of much more complex queries involving joins, subqueries, and more.

Conclusion

Single Table Inheritance is a useful pattern for modeling object hierarchies in a relational database. With TypeORM and PostgreSQL, implementing Single Table Inheritance is straightforward and efficient.

In this tutorial, we've seen how to set up a TypeORM project with PostgreSQL, define entity classes with Single Table Inheritance, and perform basic CRUD operations on those entities. We've also seen how to use TypeORM's query builder API to build complex queries.

With this knowledge, you should be able to implement Single Table Inheritance in your own TypeORM and PostgreSQL projects.

Did you find this article valuable?

Support Gayan Hewa by becoming a sponsor. Any amount is appreciated!