Nestjs🐺⚡ | The framework of Nodejs (Part-3) | Database Integration, TypeORM
Tags: javascript, typescript, webdev, tutorial
Published: 2021-09-05 12:12:16

> If you haven't read [part-1](https://dev.to/krtirtho/nestjs-the-framework-of-nodejs-part-1-gl7) & [part-2](https://dev.to/krtirtho/nestjs-the-framework-of-nodejs-part-2-5ek2), please first read those else you'll feel this article is out of context

In this part, I'll be discussing

- Nestjs Database integration
- Basic Typeorm

## Database Integration

A major part of a backend service/API is its Database Management System

> **Important**: This article is about Nestjs, not about Databases (SQL/NoSQL) so I'm assuming, one reading this will have basic knowledge about DBMS

As Nestjs is only an abstraction over typical Nodejs server APIs/Package, it supports all kinds of popular databases & most of their ORMs. It supports the following database drivers & ORMs:

- Typeorm (SQL/NoSQL )
- MikroORM (SQL/NoSQL)
- Knex
- Prisma
- mongoose (NoSQL)

I'm using TypeORM here as IMO it matches the API scheme of Nestjs the most because of its decorator pattern. Also using PostgreSQL as the Database. You can use other databases & ORMs if you want. The configuration pattern of all of them is mostly the same. Also, there's always an official/3rd-party package & documentation for your choice. Just google for it

To get started first install:

```bash
# for npm users*
$ npm i @nestjs/typeorm typeorm psql
# for yarn user
$ yarn add @nestjs/typeorm typeorm psql
```

> **Note!:** Install PostgreSQL driver or the DB driver you've chosen first in your OS. For PostgreSQL, installation instructions can be found [here](https://www.postgresql.org/download/)

Now create following files in the root of the project:

- `.env` (for storing database credentials & secrets)
- `config.ts` (for importing the env vars)
- `ormconfig.ts` (database connection configurations)

```python
#### .env #####
POSTGRES_PASSWORD=simplepassword
POSTGRES_DB=hello
NODE_ENV=development
DATABASE_USERNAME=postgres # you can put your username of your OS
DATABASE_HOST=localhost # use `postgres` if using PostgreSQL Docker Container
DATABASE_PORT=5432
PORT=4000
```

Now import this environmental variables & re-export for the project

```tsx
///// config.ts //////
export const NODE_ENV = process.env.NODE_ENV;
// all the env vars
export const DATABASE_HOST = process.env.DATABASE_HOST;
export const DATABASE_PORT = process.env.DATABASE_PORT
    ? parseInt(process.env.DATABASE_PORT)
    : undefined;
export const DATABASE_NAME = process.env.POSTGRES_DB;
export const DATABASE_PASSWORD = process.env.POSTGRES_PASSWORD;
export const DATABASE_USERNAME = process.env.DATABASE_USERNAME;
export const PORT = process.env.PORT ?? 4000;
```

Create the Database connection:

```tsx
///// ormconfig.ts /////
import {
    DATABASE_HOST,
    DATABASE_NAME,
    DATABASE_PASSWORD,
    DATABASE_PORT,
    DATABASE_USERNAME,
    NODE_ENV,
} from "./config";
import { PostgresConnectionOptions } from "typeorm/driver/postgres/PostgresConnectionOptions";

// db configuration for the orm
const ormconfig: PostgresConnectionOptions = {
    type: "postgres", // name of db you'll be using
    username: DATABASE_USERNAME,
    database: DATABASE_NAME,
    host: DATABASE_HOST,
    port: DATABASE_PORT,
    password: DATABASE_PASSWORD,
    uuidExtension: "uuid-ossp", // for using `uuid` as the type for Primary-Column `id` column
    synchronize: NODE_ENV !== "production",
};

export = ormconfig;
```

Now generate a module named `database` where all the database-related configuration/files will be saved. The following command will generate it:

```bash
$ npx nest g module database
```

Inside `database.module.ts` register database connection of the configuration using TypeORM:

```tsx
///// database.module.ts //////

import { Module } from '@nestjs/common';
import ormconfig from "../../ormconfig";
import { TypeOrmModule } from "@nestjs/typeorm";

@Module({
  imports: [
       // registers Database config
        TypeOrmModule.forRoot({
            ...ormconfig, //db config
            entities: [], // put the constructor of all classes that are an Entity
        }),
    ],
})
export class DatabaseModule {}
```

Hopefully, after restarting your application, your API/service will be connected with the Database

> **Tip!**: You can pass an Array of ORM configurations in `TypeOrmModule.forRoot()` function for multiple database connections. It allows using any type of database together

## TypeORM

> This tutorial isn't about TypeORM but I'll give a little explanation of it to get started. You can find more information about it in their [docs](https://typeorm.io/)

TypeORM API for SQL/NoSQL varies. Here I'll be showing the SQL part only. If you're using NoSQL DB e.g. MongoDB with TypeORM then you can learn it from [here](https://typeorm.io/#/mongodb)

If you had read **part-2,** you may know, there I was using a class property as an In-memory temporary database. Now we'll reflector that part to use the new PostgreSQL DB with TypeOrm

First, create `src/database/entities/hello-record.entity.ts`, then create a TypeORM schema:

```tsx
///// hello-record.entity.ts /////

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity('hello-record')
export class HelloRecord {
  @PrimaryGeneratedColumn('uuid')
  id!: string;

  @Column('varchar', { length: 16 })
  from!: string;

  @Column('text')
  msg!: string;
}
```

You can declare a class a TypeORM entity using `@Entity()` decorator. You can give the entity a name (which you should always do) or let typeorm create a name from the class's displayName

To create a primary column, `@PrimaryGeneratedColumn` is used. You can use `rowId`, `incremented`, or `uuid` as your primary column type. **Remember to enable the UUID extensions of your PostgreSQL Database or any SQL database for using** `uuid`

To create a Column, `@Column` decorator is used. You can specify the type of the column or anything of that column. I used 16 characters long `varchar` for column "`from`" because it'd be the IP address of the user who has posted a *hello message.* Also, I used the type `text` for the "`msg`" column as we don't want to limit anyone to only 240 characters long like some social media. That's inhumanity🤐

Now to let TypeORM know `HelloRecord` exists, we've to put it in the `entities` array of `Typeorm.forRoot()` function in `database.module.ts`. You've to put all entities that you're gonna use in the application in that array. BTW, if you're using multiple database connections, put entities, that are specifically created for the specific database in the specific database's configuration object's `entities` array. The same entity won't work for multiple databases

```tsx
///// database.module.ts //////

// .... (other imported stuffs)
import { HelloRecord } from './entities/hello-record.entity';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      ...ormconfig,
      // put all the entities related to the database in here
      entities: [
        HelloRecord,
      ],
    }),
  ],
})
export class DatabaseModule {}
```

> **Important!**: Never use inheritance in entity/schema declaration. If some of your entities share the same type of properties/columns, don't make a common class for them & then inherit from it. It can cause schema synchronization issues as TypeORM won't be able to know the type of that parent class's properties. Schema inheritance is not DRY instead it's an overuse of DRY (Don't Repeat Yourself). So one should never do that

Now that we've created the entity, let's use it in our `HelloService`. But we've to import it in `HelloModule` to let Nest know it belongs to `HelloModule`

```tsx
////// hello.module.ts //////

// .... (other imported stuff)
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    forwardRef(() => HiModule),
    // labelling the entity as `HelloModule`'s Repository
    TypeOrmModule.forFeature([HelloRecord]),
  ],
  providers: [HelloService, ByeService],
  controllers: [HelloController],
  exports: [HelloService],
})
export class HelloModule {}
```

`TypeOrmModule.forFeature` will give access to the `HelloRecord` entity in all providers/controllers of `HelloModule`. BTW, you can't label the same entity in different modules multiple times. If you want to have access to that entity in other modules, just import the *provider* that is using that *entity*

Now let's refactor `HelloService` to use the new Entity to save, modify & read the *hello messages:*

```tsx
////// hello.service.ts ///////

import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { HiService } from 'src/hi/hi.service';
import { ByeService } from './bye.service';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { HelloRecord } from '../database/entities/hello-record.entity';

@Injectable()
export class HelloService {
  constructor(
    @Inject(forwardRef(() => HiService))
    private hiService: HiService,
    @Inject(forwardRef(() => ByeService))
    private byeService: ByeService,
    @InjectRepository(HelloRecord)
    private helloRecordRepo: Repository<HelloRecord>,
  ) {}

	async findById(id: string) {
    return await this.helloRecordRepo.findOneOrFail({ id });
  }

  async create(msg: string, ip: string) {
    const newMsg = this.helloRecordRepo.create({ msg, from: ip });
    return await newMsg.save();
  }

  async deleteById(id: string) {
    return await this.helloRecordRepo.delete({ id });
  }

  getHello(arg: string) {
    return `hello for ${arg}`;
  }

  // a method that uses `hiService`
  hiServiceUsingMethod() {
    return this.hiService.getHi('hello');
  }

  byeServiceUsingMethod() {
    return this.byeService.getBye('hello');
  }

```

TypeORM provides all the necessary methods to create-delete-modify data. Below are some of those that are used mostly:

- `EntityName.findOne` (Finds by criteria & returns the first matched record as a Promise)
- `EntityName.findOneOrFail` (Just like `findOne` but throws an error if no record is found. Always try using it instead of `findOne` as it supports error-handling)
- `EntityName.find` (finds all the records matching criteria & returns as a Promise)
- `EntityName.save` (saves any object passed to it matching the schema of that entity. Can be used to modify/update a record too)
- `EntityName.create` (creates a new soft record which will be passed as a parameter to `EntityName.save`)
- `EntityName.delete` (deletes all the records matching the passed criteria)
- `EntityName.createQueryBuilder` (Alternative query API that uses strings to manipulate SQL transactions instead of using Object-Oriented approach. It's more like a functional approach. It follows the popular builder-pattern & supports method chaining. It's closer to native SQL)

Working final Application:

{% codesandbox hello-world-part-3-5xpn3 %}
Here, I end the **Nestjs🐺⚡ | The framework of Nodejs** series

Nestjs is an awesome backend framework providing all the necessary tools needed for developing enterprise-grade, reliable server applications/APIs. This series is only a higher-level overview of Nestjs where very few concepts & features of Nestjs were covered. Nestjs offers much more than that. Nest's official [docs](http://docs.nestjs.com) provide a much deeper & clearer overview & tutorial about Nestjs. They also provide Enterprise support

> Nestjs is not a tool/framework/library only, it's a guide for you too