One of the most popular Object Relational Mapping or ORM is TypeORM. At first lets talk a bit about ORM. As the name suggests, ORM is a programming technique that relates or maps object oriented model to relational database model. In a nutshell, you can craete or query data from a relational database using object oriented way with ORM. Almost all of the object oriented programming languages support ORM and their frameworks use ORM e.g. django
in python, laravel
in php, nestjs
in node etc. ORM helps us in many ways. It helps us by revoking the direct use of writing sql queries.
Some of the main benefits of using ORM includes:
- It speeds up the development by eliminating repetitive usage of SQL code
- It reduces development time a lot
- It overcomes all the vendor spicific SQL differences because it knows how to convert to vendor SQL code
- It can be used with both relational databases like mysql, oracle, postgresql, maria db and nosql like mongodb
- It abstracts things like caching and indexing
- It can catch general issues like input validations
Now lets go to our main topic TypeORM
. The official documentation says:
TypeORM is an ORM that can run in NodeJS, Browser, Cordova, PhoneGap, Ionic, React Native, NativeScript, Expo, and Electron platforms and can be used with TypeScript and JavaScript (ES5, ES6, ES7, ES8).
Features
Lets find out some basic features of typeORM:
- entities and columns
- database-specific column types
- entity manager
- repositories and custom repositories
- clean object relational model
- associations (relations)
- eager and lazy relations
- uni-directional, bi-directional and self-referenced relations
- supports multiple inheritance patterns
- cascades
- indices
- transactions
- migrations and automatic migrations generation
- connection pooling
- replication
- using multiple database connections
More features can be found in the official documentation
Installation
We can install TypeORM from npm
registry very easily with the following command
npm i -g typeorm
Here i
is the alias for install
and -g
means we are installing it globally in our machine. In order to use it in a project we need to omit -g
. In addition we can install two optional modules named reflect-metadata
and @types/node
as follows
npm i reflect-metadata @types/node
Now according to the database you would like connect, just install its module. Suppose I would like to connect to mysql db so I will do the following
npm i mysql
Connection
In order to interact to a database we need to create connection of TypeORM with that particular database. This connection can be created in several ways. One way to achieve this is using createConnection
method. Let us suppose that we would like to connect to a mysql db which can be achieved in the following way
import { createConnection } from "typeorm";
const connection = await createConnection({
type: "mysql",
host: "localhost",
port: 3306,
username: "admin",
password: "12345",
database: "blog-world"
});
We can connect with multiple databases using createConnections
method which takes an array of object as input where we can use our different databases.
Another way of connecting to a database is to use ormconfig.json
file where all those object key value prperties will be stored as json format and exported when needed. I personally prefer this way.
Entity
Entity is a class which will represent a table of our database or document in case of mongodb. This means all the properties of an entity get converted to columns of a table. Entity supports all basic types like number
, string
, boolean
, array
and even an object of other entity. Lets build a writer
entity for our blog-world
database.
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
@Entity()
export class Writer {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
phone: string;
@Column()
isFeatured: boolean;
}
Here each property tagged with @Column
represents a column in db table. There can be different types of columns based on its nature. A model must have a Primary Column which will represent that row. If it is auto incremented then @PrimaryGeneratedColumn
needs to be used. We can also use @PrimaryGeneratedColumn("uuid")
to generate unique id everytime. In that case type of id will be string instead of number. There can be other columns like @CreateDateColumn
which automatically sets entity's insertion date. Based on different databases Spatial Columns
can be used too. We can use enum
as type of a column. Let us add a column named gender to our above entity.
export enum Gender {
MALE = "male",
FEMALE = "female",
OTHER = "other"
}
@Entity
export class Writer {
// other properties will remain same
@Column({
type: "enum",
enum: Gender
})
gender: Gender;
}
Relations
In order to add reference of another entity to an entity we need to use relations. Relations can be one-to-one
, one-to-many
, many-to-one
and many-to-many
. TypeORM has support for all of these relations using @OneToOne
, @OneToMany
, @ManyToOne
and @ManyToMany
respectively. Lets see its implementation with code. At first we create an Avatar
entity. Here each writer can have a single avatar and we would like to assign one avatar to a single writer which represents a one-to-one
relation.
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
@Entity()
export class Avatar {
@PrimaryGeneratedColumn()
id: number;
@Column()
url: string;
@Column()
text: string;
}
Now in our Writer entity we need to add this Avatar property along with necessary relation decorator.
import {Entity, OneToOne, JoinColumn} from "typeorm";
import {Avatar} from "./Avatar";
@Entity
export class Writer {
// other properties will remain same
@OneToOne(() => Avatar)
@JoinColumn()
avatar: Avatar;
}
In order to establish one-to-many
and many-to-one
relations we will create a new entity named Blog. A writer can have many blogs but one blog will be written by one writer only which satisfies our requirement.
import {Entity, PrimaryGeneratedColumn, Column, ManyToOne} from "typeorm";
import {Writer} from "./Writer";
@Entity()
export class Blog {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
cover: string;
@Column()
content: string;
@ManyToOne(() => Writer, writer => writer.blogs)
writer: Writer;
}
Now we update Writer entity as follows to establish one-to-many
relation.
import {Entity, OneToMany} from "typeorm";
import {Blog} from "./Blog";
@Entity
export class Writer {
// other properties will remain same
@OneToMany(() => Blog, blog => blog.writer)
blogs: Blog[];
}
Now we are left with many-to-many
relation which can be established by adding another entity named Category. A blog can have multiple categories and a category can be assigned to multiple blogs too.
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
@Entity()
export class Category {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
}
Now we will update Writer entity as follows
import {Entity, ManyToMany, JoinTable} from "typeorm";
import {Category} from "./Category";
@Entity
export class Writer {
// other properties will remain same
@ManyToMany(() => Category)
@JoinTable()
categories: Category[];
}
@JoinTable()
is required for @ManyToMany
relations. We need to put @JoinTable
on one (owning) side of relation.
CRUD Operations
In TypeORM we can perform CRUD operations using EntityManager, Repository and Query Builder. EntityManager and Repository are quite similar in syntax while performing necessary operations. The difference is that an Entity Manager handles all entities, while Repository handles a single entity which means that when using an Entity Manager we need to specify the Entity we are working with for each method call. Let us explain it with an example. Suppose we would like to fetch a Writer with a id given.
import {getManager} from "typeorm";
import {Writer} from "./entity/Writer";
// using entity manager
const entityManager = getManager();
const writer = await entityManager.findOne(Writer, 1);
// using repository
const writerRepository = getRepository(Writer);
const writer = await writerRepository.findOne(1);
// now lets update writer's name
writer.name = "Zlatan Ibrahimovic";
await entityManager.save(writer);
await writerRepository.save(writer);
This is a simple task performed but other tasks like joining and complex operations can also be performed with the help of both entity manager and repository. Now lets talk a bit about query builder. According to the documentation:
QueryBuilder is one of the most powerful features of TypeORM - it allows you to build SQL queries using elegant and convenient syntax, execute them and get automatically transformed entities.
So we can have an idea that how powerful query builder is. Its time for some coding examples again. We perform the above query i.e. find out the first writer using query builder.
const firstWriter = await connection
.getRepository(Writer)
.createQueryBuilder("writer")
.where("writer.id = :id", { id: 1 })
.getOne();
We can also perform join queries using query builder very easily. Almost all sql queries we perform can be done in TypeORM but in many cases like very complex queries, it can be difficult.
In this post I have tried to discuss the basic topics of TypeORM briefly. Advanced topics like Migration, Indices, Transactions, Listeners, Subscribers etc have not been discussed here. The official document is very elaborative and nicely explained with lots of examples. You are highly encouraged to have a look at it.
Happy Coding πππππ