What is TypeORM?
Understanding how relationships work in database management is an important concept for a software engineer to understand. We will learn what typeORM is, how relationships work in typeORM, and the importance of relationships in database management in this article.
If we are creating a typescript application and need a database for backend services, we will need an ORM (Object Relational Mapper) to connect our application to the database. Object denotes the model in your application, Relational denotes the relationship between tables in a Relational Database Management System(RDMS), and Mapping denotes the act of connecting the model and our tables.
TypeORM is a TypeScript ORM (object-relational mapper) library that simplifies the integration of your TypeScript application with a relational database. MySQL, SQLite, Postgres, MS SQL Server, and a variety of other relational databases are supported by this library.
Relationships in TypeORM
In TypeORM, relationships exist between tables in the database. When one of the tables has a foreign key that references the primary key of the other table, a relationship exists. This feature enhances the power and efficiency with which relational databases store information.
TypeORM enables entities to be related to one another and, as a result, to database tables.
Entities are building blocks of a database.
In TypeORM, an Entity is a class that defines a collection of fields or columns as well as database operations. It is made up of columns and relationships.
In general, relationships can be divided into four types.
One-to-one relationship
One-to-many relationship
Many-to-one relationship
Many-to-many relationship
One-to-one relationship
When an instance of one table, say Table A, contains an instance of another table, say Table B, a one-to-one relationship is formed. For instance, we could use a user's post on a website. The requirement here is that a user can only make one post.
We'd need two tables here: User table and Post table. The User table will contain the user's information, and the Post table will contain the post information as well as the user information who made the post on the site.
To accomplish this, we'll need two entities: User entity and Post entity.
//User.entity.ts
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
}
Result
The User entity has an id
column which is a primarily generated column with an auto-increment value that is automatically generated. It also has a name
column that is string-based.
//Post.entity.ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
OneToOne,
JoinColumn} from "typeorm";
import {User} from "./User.entity";
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
post: string;
@OneToOne(type => User)
@JoinColumn()
user: User;
}
Result
The Post entity has an id
which is a primarily generated column and a string typed column called post
. It has a third column that is joined to the User entity via the @JoinColumn
decorator. A one-to-one relationship with the type directed to the User entity is established in this column.
@JoinColumn
specifies which side of the relation contains the join column with a foreign key. It allows you to customise the names of the join column and the referenced column.
When we use @JoinColumn
on a column, the database creates a column by default named propertyName + referencedColumnName. In the example used above, the property name is user while the referenced column name is by default id of the User entity, therefore a new column called userId
is created as shown in the result above.
However, the column name can be explicitly set instead of the default naming convention by using
@JoinColumn({ name: "user_post_id" })
//Post.entity.ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
OneToOne,
JoinColumn,
} from 'typeorm';
import { User } from './User.entity';
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
post: string;
@OneToOne((type) => User)
@JoinColumn({ name: "user_post_id" })
user: User;
}
In the code snippet above, the @JoinColumn
had a name property added, therefore the new column created will be user_post_id
instead of the default name of userId
.
Result
The
@JoinColumn
always refers to the primary column of the related entity by default.
If we want to create a relationship with other columns of the related entity, we can do so as:
//User.entity.ts
import { Entity, PrimaryColumn, Column } from 'typeorm';
@Entity()
export class User {
@Column()
id: number;
@PrimaryColumn()
name: string;
}
This entity is quite different from the one created above in that the name is the primary column and not the id column. This is necessary because we intend to reference name column in the JoinColumn for Post entity below and it has been earlier stated that the @JoinColumn
can only reference a primary column.
//Post.entity.ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
OneToOne,
JoinColumn, } from 'typeorm';
import { User } from './User.entity;
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
post: string;
@OneToOne((type) => User)
@JoinColumn({ referencedColumnName: 'name' })
user: User;
}
Result
One-to-many relationship
When one row in one table, say Table A, is linked to multiple rows in another table, say Table B, but only one row in Table B is linked to one row in Table A, a one-to-many relationship is formed.
In the case of a Customer and an account entity, a customer can own multiple accounts, whereas an account can only be owned by one customer.
//Customer.entity.ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
OneToManyEntity } from typeorm';
import { Account } from './Account.entity;
@Entity()
export class Customer {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(() => Account, (account) => account.customer)
account: Account[];
}
Result
The Customer entity has a name column and has a one-to-many relationship because one customer can have many account. We added @OneToMany
to the account property in the customer entity and specified Account as the target relation type.
It is important to note that the
@JoinColumn
can be omitted in a many-to-one or one-to-many relationship.
//Account.entity.ts
import {
Entity,
PrimaryGeneratedColumn,
ManyToOne
} from 'typeorm';
import { Customer } from './Customer.model';
@Entity()
export class Account {
@PrimaryGeneratedColumn()
id: number;
@ManyToOne(() => Customer, (customer) => customer.account)
customer: string;
}
Result
The Account entity has a many-to-one relationship because multiple accounts could belong to one customer. We added @ManyToOne
to the customer property in the Account entity and specified Customer as the target relation type.
@ManyToOne
cannot exist without@OneToMany
as it is required if@OneToMany
needs to be used.
Many-to-one relationship
This relationship is similar to a one-to-many relationship but from a different perspective. When multiple rows in one table are linked to a single row in another table, a many-to-one relationship is formed.
Looking at a Customer and an Account entity, where a customer can own many accounts in a one-to-many relationship, it is also true that multiple accounts can belong to a single customer in a many-to-one relationship.
Multiple entries in the account table can belong to the same row in the customer table. An example is quite similar to the one-to-many relationship explained above.
Many-to-many relationship
When multiple rows in one table are linked to multiple rows in another table, a many-to-many relationship is formed.
In a Customer and Product entity, for example, a customer can buy more than one product, and a product can be bought by more than one customer.
//Product.entity.ts
import {
Entity,
PrimaryGeneratedColumn,
Column } from "typeorm"
@Entity()
export class Product {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
}
Result
//Customer.entity.ts
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToMany,
JoinTable
} from "typeorm"
import { Product } from "./Product.entity"
@Entity()
export class Customer {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@ManyToMany(() => Product)
@JoinTable()
product: Product[]
}
The Customer and the Product entity are quite similar except the Many-to-many relationship built on the product column of the Customer table. This table is joined with the Product table using a @JoinTable
decorator.
@JoinTable
should be added to one of the entities, typically the owning entity.
Due to this decorator, a new table will be created with the default name as table_tableProperty_referencedTable. In our example, this table will be created as customer_product_product
.
In this new table, there are two columns which are customerId
and productId
. Data stored in this table are entries of a customer id and multiple products bought by such customer and also entries of product id and customers who bought them.
Result
Why is this knowledge important?
The significance of this knowledge cannot be overstated, as relationships are the logic that underpins business rules. Databases are generally about table interaction, and by understanding how the interaction/relationships work, you can begin to make more meaningful business designs as a developer. It aids in the avoidance of many schema errors in database design, as well as the reduction of future limitations in the enhancement of business features.
Conclusion
In this article, we learned how relationships work and the different types of database relationships in typeORM. We also briefly discussed why understanding relationships is important in database management.