If you haven't read part-1 & part-2, 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:
# 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
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)
#### .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
///// 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:
///// 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:
$ npx nest g module database
Inside database.module.ts
register database connection of the configuration using TypeORM:
///// 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
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
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:
///// 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
///// 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
////// 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:
////// 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 likefindOne
but throws an error if no record is found. Always try using it instead offindOne
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 toEntityName.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:
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 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