TypeORM With NEST JS Basic Tutorial

TypeORM With NEST JS Basic Tutorial
TypeORM With NEST JS Basic Tutorial

In this article, we will be using TypeORM with Nest Js to integrate the database with our application. But before starting with TypeORM, let’s have a brief look at the concept of Object-relational mapping(ORM).

Object-relational mapping as a technique for converting data between incompatible type systems using object-oriented programming languages. In other words, ORM is a programming technique in which a metadata descriptor is used to connect object code to a relational database.


Source Wikipedia

Object code is written in object-oriented programming (OOP) languages such as C++, JAVA, etc. We will be using TypeScript for the creation of our object-oriented programming.

In addition to the data access technique, ORM also provides simplified development because it automates object-to-table and table-to-object conversion, resulting in lower development and maintenance costs.

Now, that we have a good idea about what the notion of ORM is, let’s understand what TypeORM is :

What is TypeORM?

TypeORM: 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).

Topics:

  1. Creating a model( or Table ).
  2. Primary / Auto-generation column.
  3. Relationship between two or more models.
  4. Our Project.

Creating a model/ Table

The first step in the database is to create a table. With TypeORM, we create database tables through models. So models in our app will be our database tables.

Now create a sample model “Cat” for a better understanding.


export class Cat {
    id: number;
    name: string;
    breed: string;
    age: string;
}

Note: The database table is not created for each model but only for those models which are declared as entities. To declare a model as an entity, we just need to add the ‘@Entity()‘ decorator before the declaration of the Class defining our model.

In addition to this, we should ideally have columns in our model now because the table which will be generated (because of the model being declared as an entity now) makes no sense without any column in it. To add a data member of a model as a column, we need to decorate a data member with a ‘@Column()‘ decorator.

Let us modify our above model of ‘Cats’ by adding ‘@Entity()’ and ‘@Column()’ decorator.


@Entity()
export class Cat {

    @Column()
    id: number;

    @Column()
    name: string;

    @Column()
    breed: string;
    
    @Column()
    age: string;

}

Primary / auto-generated primary column

For creating a column as a primary key of the database table, we need to use the ‘@PrimaryColumn()’ decorator instead of the ‘@Column()‘ decorator. And for the primary column to be self-generated, we need to use ‘@PrimaryGeneratedColumn()‘ instead of ‘@PrimaryColumn()‘.

By making ‘id’ in ‘Cat’ as an auto-generated primary key, our Cat model will look like this:


@Entity()
export class Cat {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @Column()
    breed: string;
    
    @Column()
    age: string;

}

Relationship between Two or More Models

A relationship, in the context of databases, is a situation that exists between two relational database tables when one table has a foreign key that references the primary key of the other table. Relationships allow relational databases to split and store data in different tables while linking disparate data items.

There are 3 types of relationships in relational database design:

  • One-to-One (implemented by ‘@OneToOne()‘ decorator)
  • One-to-Many / Many-to-One (implemented by ‘@OneToMany()‘ decorator )
  • Many-to-Many (implemented by ‘@ManyToMany()‘ decorator)

One-to-One
One-to-Many
One-to-Many
Many-to-Many
Many-to-Many

Our Project

In this section, we will create a NestJS project in which we will have three tables/entities as follows:

  • UserEntity
  • BookEntity
  • GenreEntity

Relationships between the entities:

  • UserEntity and BookEntity: One-To-Many
  • BookEntity and GenreEntity: Many-To-Many

In simple words, a user can have many books and each book can belong to more than one Genre.

For now, we will create the above-mentioned entities as follows without any relationship between them as follows:

MyProject/db/user.entity.ts


import { Entity, PrimaryGeneratedColumn, Column, BaseEntity, OneToMany } from 'typeorm';
import BookEntity from './book.entity';
@Entity()
export default class UserEntity extends BaseEntity {

  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 500 })
  name: string;
}

MyProject/db/book.entity.ts


import { Entity, PrimaryGeneratedColumn, Column, BaseEntity, ManyToOne, ManyToMany, JoinTable } from 'typeorm';
import UserEntity from './user.entity';
import GenreEntity from './genre.entity';

@Entity()
export default class BookEntity extends BaseEntity 
{
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 500 })
  name: string;
}

MyProject/db/genre.entity.ts


@Entity()
export default class GenreEntity extends BaseEntity {

  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  type: string;

}

For setting up the relation between UserEntity and BookEntity, we have to add the following code in UserEntity and BookEntity Class as follows:

MyProject/db/user.entity.ts


// 1:n relation with bookEntity 
  @OneToMany( type => BookEntity , book => book.user)
  books: BookEntity[];

type => BookEntity is a function that returns the class of the entity with which we want to make our relationship.

book => book.user states which column is to be used by ‘BookEntity’ to get the associated user.

Now, we have set a One-to-Many relationship from the ‘UserEntity’ side. As One-to-Many is complimentary with Many-to-One, we should state the ‘Many-to-One’ relationship from BookEntity to UserEntity.

MyProject/db/book.entity.ts


// n:1 relation with books
  @ManyToOne(type => UserEntity, user => user.books)
  user: UserEntity;

Similarly, to make a many-to-many relationship between BookEntity and GenreEntity, we have to add the following code:


// n:n relation with genre
  @ManyToMany(type => GenreEntity)
  @JoinTable()
  genres: GenreEntity[];

Here, the ‘@JoinTable()’ decorator states that in a many-to-many relationship between BookEntity and GenreEntity, the ownership lies on the BookEntity side.

Now we are done with almost everything related to the database and TypeORM. The only thing that remains is to establish a connection with the database. For this purpose, we have to create an ‘ormconfig.json’ file and add the following JSON code to it.

MyProject/ormconfig.json


{
  "type": "sqlite",
  "database": "./database.sqlite",
  "synchronize": "true",
  "entities": [
    "dist/db/entity/**/*.js"
  ],
  "cli": {
    "entitiesDir": "src/db/entity"
  }
}

The first line in the above JSON object specifies that the database we are using is ‘SQLite’.

Now we have to create the NEST controllers and services to handle the requests.

Here are the three DataTransferObjects we will be using in the further code:

MyProject/User/dto/create-user.dto.ts


export default class CreateUserDto {
  readonly name: string;
  readonly books: number[] ;
}

MyProject/User/dto/create-book.dto.ts


export default class CreateBookDto {
  readonly name: string;
  readonly userID: number;
  readonly genreIDs: number[];
}

MyProject/User/dto/create-genre.dto.ts


export default class CreateGenreDto {
  readonly type: string;
}

Below are the Controllers and Services for Users, Books, and Genres that will be handling the requests.

Users

MyProject/User/user.controller.ts


import { Body, Controller, Get, ParseIntPipe, Post, Put } from '@nestjs/common';
import { UserServices } from './user.services';
import CreateUserDto from './dto/create-user.dto';

@Controller('users')
export class UserController {
  constructor(private readonly usersServices: UserServices) {}

//'postUser()' will handle the creating of new User
  @Post('post')
  postUser( @Body() user: CreateUserDto) {
    return this.usersServices.insert(user);
  }
// 'getAll()' returns the list of all the existing users in the database
  @Get()
  getAll() {
    return this.usersServices.getAllUsers();
  }

//'getBooks()' return all the books which are associated with the user 
// provided through 'userID' by the request  
  @Get('books')
  getBooks( @Body('userID', ParseIntPipe) userID: number ) {
    return this.usersServices.getBooksOfUser(userID);
  }
}

Myproject/User/user.service.ts


import { Injectable } from '@nestjs/common';
import UserEntity from '../db/entity/user.entity';
import CreateUserDto from './dto/create-user.dto';
import BookEntity from '../db/entity/book.entity';
import {getConnection} from "typeorm";

@Injectable()
export class UserServices {

  async insert(userDetails: CreateUserDto): Promise<UserEntity> {
    const userEntity: UserEntity = UserEntity.create();
    const {name } = userDetails;
    userEntity.name = name;
    await UserEntity.save(userEntity);
    return userEntity;
  }
  async getAllUsers(): Promise<UserEntity[]> {
    return await UserEntity.find();
  }
  async getBooksOfUser(userID: number): Promise<BookEntity[]> {
    console.log(typeof(userID));
    const user: UserEntity = await UserEntity.findOne({where: {id: userID}, relations: ['books']});
    return user.books;
  }
}

Myproject/User/user.module.ts


import { Module } from '@nestjs/common';
import { UserServices } from './user.services';
import { UserController } from './user.controller';
@Module({
  imports: [],
  controllers: [UserController],
  providers: [UserServices],
})
export class UserModule {}

Genre

MyProject/Genre/genre.controller.ts


import { Body, Controller, Get, Post } from '@nestjs/common';
import GenreServices from './genre.services';
import CreateGenreDto from './dto/create-genre.dto';

@Controller('genre')
export default class GenreController {
  constructor(private readonly genreServices: GenreServices) {}
  @Post('post')
  postGenre( @Body() genre: CreateGenreDto) {
    return this.genreServices.insert(genre);
  }
  @Get()
  getAll() {
    return this.genreServices.getAllGenre();
  }
}

MyProject/Genre/genre.services.ts


import { Injectable } from '@nestjs/common';
import CreateGenreDto from './dto/create-genre.dto';
import GenreEntity from '../db/entity/genre.entity';

@Injectable()
export default class GenreServices {
    async insert(genreDetails: CreateGenreDto): Promise<GenreEntity> {

    const genreEntity: GenreEntity = GenreEntity.create();
    const {type} = genreDetails;

    genreEntity.type = type;
    await GenreEntity.save(genreEntity);
    return genreEntity;
  }
  async getAllGenre(): Promise<GenreEntity[]> {
        return await GenreEntity.find();
  }
}

MyProject/Genre/genre.module.ts


import { Module } from '@nestjs/common';
import GenreServices from './genre.services';
import GenreController from './genre.controller';
@Module({
  imports: [],
  controllers: [GenreController],
  providers: [GenreServices],
})
export default class GenreModule {}

Books

MyProject/Books/books.controller.ts


import BookEntity from '../db/entity/book.entity';
import CreateBookDto from './dto/create-book.dto';
import UserEntity from '../db/entity/user.entity';
import { createQueryBuilder, getConnection } from 'typeorm';
import GenreEntity from '../db/entity/genre.entity';

export class BooksService {

  async insert(bookDetails: CreateBookDto): Promise<BookEntity> {
    const { name , userID , genreIDs } = bookDetails;
    const book = new BookEntity();
    book.name = name;
    book.user = await UserEntity.findOne(userID) ;
    book.genres=[];
    for ( let i = 0; i < genreIDs.length ; i++)
    {
             const genre = await GenreEntity.findOne(genreIDs[i]);
             book.genres.push(genre);
    }
    await book.save();
    return book;
  }
  async getAllBooks(): Promise<BookEntity[] > {
    // const user: UserEntity = await UserEntity.findOne({where: {id: 2}, relations: ['books']});
    return BookEntity.find();
  }

MyProject/Books/books.module.ts


import { Module } from '@nestjs/common';
import { BooksService } from './books.service';
import BooksController from './books.controller';
@Module({
  imports: [],
  controllers: [BooksController],
  providers: [BooksService],
})
export default class BooksModule {}

Now, finally, this is the time to integrate everything with ‘app.module.ts’


import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './User/user.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import UserEntity from './db/entity/user.entity';
import BooksModule from './Books/books.module';
import GenreModule from './Genre/genre.module';
import BookEntity from './db/entity/book.entity';
import GenreEntity from './db/entity/genre.entity';

@Module({
  imports: [UserModule ,
            BooksModule,
            GenreModule,
    TypeOrmModule.forFeature(
      [UserEntity, BookEntity , GenreEntity],
    ),

    TypeOrmModule.forRoot(),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}


In A Nutshell…

TypeORM is a type-safe ORM for Node.js and io.js. It provides a simple, intuitive interface for working with your data, and integrates with NEST to provide type safety and autocompletion.

In this tutorial, we showed you how to set up TypeORM with a NESTJS project and how to use the two together. Let us know your thoughts in the comment section!

What is TypeORM?

TypeORM is a TypeScript ORM (object-relational mapper) library that makes it easy to link your TypeScript application up to a relational database. TypeORM supports MySQL, SQlite, Postgres, MS SQL Server, and a host of other traditional options.

List one major benefit of TypeORM?

High-quality and loosely coupled applications. Scalable applications. Easily integrate with other modules. Perfectly fits any architecture from small to enterprise apps.

Why should we use TypeORM?

TypeORM supports both Active Record and Data Mapper patterns, unlike all other JavaScript ORMs currently in existence, which means you can write high-quality, loosely coupled, scalable, maintainable applications in the most productive way.