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:
- Install TypeORM and its dependencies using npm or yarn:
npm install typeorm reflect-metadata pg
-
Create a new TypeScript file to define our entities. We’ll call this file
entities.ts
. -
Create a new PostgreSQL database to store our data.
-
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.
- 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.