A Simple Guide To TypeORM: What It Is And How To Use It
Considering that current languages have object-oriented programming features, most database management systems are based on the relational model, where there are tables instead of objects. For these reasons, the practice of mapping relational objects is of great importance in the development area.
In short, a relational database works with tables and relations between them. Meanwhile, we have several elements in the object-oriented paradigm, like classes, properties, visibility, inheritance, and interfaces. This difference in the structure of the two paradigms makes it challenging to represent the data and the model. This is where ORM comes into play to solve this problem.
In this article, we will talk about TypeORM, one of the main tools used today.
What Is an ORM?
ORM, or object-relational mapping, aims to create a mapping layer between our object model (application) and our relational model (database) in order to abstract access to the database.
In summary, it is a technique used to map between object-oriented systems and relational databases. Database tables represented in classes and table records would be instances of these classes. Therefore, in the acronym “ORM,” each of the terms is related to a part of the process:
- Object: the part used with your programming language
- Relational: a relational database management system. Although there are various kinds of databases, relational databases are the most common.
- Mapping: where the developer makes a bridge between their objects and their tables
The ORM defines a technique to perform the conciliation between the two models. One of the core parts is mapping rows to objects.
That is, the ORM defines how data will be mapped between environments and how it will be accessed and recorded. This cuts down on development time because you don’t have to spend time doing this setup. Another advantage is the adaptation of new team members. Since many projects use the same tool, finding members who are already used to the work pattern is possible.
ORM vs. Query Builder vs. Native Driver
A native driver is at the lowest level; that is, it’s closest to the database. You connect to the database, write SQL queries in string form, and tell the database to execute. Executing queries is an asynchronous operation, so they return a promise.
One level of abstraction above is the query builder. The main difference is that here you write the queries programmatically using functions, and the library takes care of generating the native query. An advantage of this approach is that the library handles possible syntax differences between different databases. This allows you to switch banks more easily in your application, making little or no code changes. One of the most popular query builder libraries on Node.js is Knex.
Object-relational mapping, as the name implies, is a pattern where the relational structure of the database is mapped into objects in the language in question. Tables turn into objects, rows turn into object attributes, and relations between tables become relations between objects. This is the highest level of abstraction. You don’t even have to think about SQL queries.
TypeORM
Now that we understand the function of an ORM, let’s focus on this framework. According to its documentation, TypeORM is an ORM that can run on Node.js, Browser, Cordova, Ionic, React Native, NativeScript, Expo, and Electron platforms and can be used with both TypeScript and JavaScript.
Entity
An entity is what you use to relate your data to a table in the database. Let’s see an example of an entity in the code below:
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity()
export class Professor {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@Column()
address: string
@Column()
isActive: boolean
}
Code language: TypeScript (typescript)
Using the @Entity
decorator, we define our new entity. In this case, it is called “Professor” and has the following attributes: ID
, which is a number type with auto-incremental values; name
, which is a string; address
, which is also a string; and isActive
, which is a boolean.
Notice the similarity between this model and a database table. This type of representation makes it much easier to visualize the entities we are working with.
You can read more about entities in the official TypeORM documentation.
Migrations
During the life of an application, and especially during its initial development stage, it is very common to have demands to migrate the database with the application so that both structures are synchronized. This process, done manually, depends on close communication with the development team so that there are no disagreements.
Another very useful feature of TypeORM is the so-called migrations.
We can create migrations that produce several tables and their attributes, so just run the migrations, and we will automatically create our tables.
import { MigrationInterface, QueryRunner, Table } from "typeorm";
export default class CreateUser implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: "users",
columns: [
{
name: "id",
type: "uuid",
isPrimary: true,
generationStrategy: "uuid",
default: "uuid_generate_v4()"
},
{
name: "name",
type: "varchar(200)"
},
{
name: "profile_id",
type: "uuid"
}
]
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable("users");
}
}
Code language: TypeScript (typescript)
Notice that there are two methods for each migration: up
and down
. You must insert the code to carry out the migration in the up
method. In the down
method, you must insert the necessary code if you want to revert the last migration.
Relations
Relations are a core concept for understanding relational databases. Therefore, relational databases are based on the relational model of organization and are made to represent and organize data in tables.
In this sense, we can have access to different types of data, such as the financial data of a company, patient information from a hospital, or the names of students at a university.
Also, when we refer to this relational model structure, in this type of database, each row in the table will represent a unique ID, which we will call a key. In addition to this, it is worth mentioning other characteristics that are part of the relational database model, such as columns, which contain attributes of each piece of data and can also have a value.
This helps build relations that interconnect the data structure in a database.
Let’s imagine we have two entities, Blog and Post, and we want to create a relationship between them. We have a one-to-many relationship between Blog and Post. This relationship is done in TypeORM using the @OneToMany
annotation like this:
@OneToMany(() => Post, (post) => post.blog)
public posts: Post[];
Code language: TypeScript (typescript)
On the side of the Post class, it looks like this, following the same parameters as @OneToMany
:
@ManyToOne(() => Blog, (blog) => blog.posts)
public blog: Blog;
Code language: TypeScript (typescript)
Keep in mind that we can also use TypeORM with nonrelational databases such as MongoDB.
Pros and cons
The first point to have in mind when considering using TypeORM is remembering that you’re using an ORM, so if your project relies heavily on a high level of query efficiency, it might not be the ideal option.
Sometimes it’s better to work with a tool that the team has already mastered than to spend time learning another one. It all depends on the specifics of the project.
However, when using TypeORM you will have access to a series of abstractions that will facilitate development and save time. In addition, one of the great benefits is the possibility of creating scalable and high-quality applications.
TypeORM also supports multiple databases, whether SQL or NoSQL. Some databases you can use are PostgreSQL, MySQL, MariaDB, and MongoDB.
Conclusion
Within a software development environment that works on a relational basis, it is interesting to consider the use of an ORM to abstract the mapping of application objects versus database objects.
The level of abstraction of this approach is relatively high, but that doesn’t mean you shouldn’t know what happens at the lowest level. The problem happens when the developer skips steps and uses an abstraction without understanding either what it abstracts or the technology behind it.
Therefore, before using TypeORM or any other ORM, it is recommended to analyze the needs of the project.
So, in a nutshell, TypeORM is a great tool for dealing with databases in the process of software development if used wisely. So make sure to really understand the necessities of the application to know where to focus more.
This post was written by Rhuan Souza. Rhuan is a software engineer who has experience with infrastructure. Rhuan is currently working as a full-stack web developer. He’s a passionate developer who focuses not only on code, but also wants to help change processes, and make people’s lives easier.