NestJS and PostgreSQL: A CRUD Tutorial

Build a robust and secure CRUD API with NestJS and PostgreSQL, using TypeORM to manage database operations.

Shakildodhiya
Simform Engineering
10 min readJun 19, 2023

--

What you will learn from this blog:

This tutorial will teach us how to use NestJS to connect to a PostgreSQL database and perform basic CRUD operations. NestJS is a powerful NodeJs framework that makes it easy to build scalable and maintainable web applications. PostgreSQL is a popular relational database known for its performance and reliability.

Prerequisite:

  • Before getting started, you should have installed the LTS version of the node on your machine. if you have not installed it yet, click here to install it.
  • You will also require basic knowledge of javascript and typescript (specially decorator).

Let’s get started:

Step 1: Install nestjs CLI globally

  • Now let’s install nest CLI. To install Nestjs CLI, you can use the below command.

while installing global package you might require sudo access.

npm i -g @nestjs/cli

Step 2: Create Nestjs App

  • Now, we will create a new project with the help of the command. To create a new project, you can use the below command.
nest new project-name
  • you can replace ‘’project-name’’ with your desired project name.
  • After that, you will have one folder with the name of your project. you can open that project in your favorite IDE.

Step 3: PostgreSQL & PgAdmin installation:

PostgreSQL: PostgreSQL is a powerful open-source relational database management system (RDBMS) known for its robustness, scalability, and extensive feature set. It supports advanced SQL queries, data integrity, and transaction management.

With its extensibility and wide range of built-in functions, PostgreSQL is widely used by businesses and developers for handling complex data requirements and building reliable applications. click here to download Postgresql.

PgAdmin: PgAdmin is a popular open-source administration and development platform for PostgreSQL databases. It offers a user-friendly graphical interface that allows users to manage and interact with PostgreSQL databases efficiently. click here to download PgAdmin.

Install the required packages

  • To perform CRUD operation, we will need the help of some packages you can install via npm. open the terminal and use the below commands.
cd `to-your-project`
npm install --save @nestjs/typeorm typeorm pg
  • We have installed 3 packages @nestjs/typeorm, typeorm, and pg. @nestjs/typerorm package is used to get typeOrm modules and other important modules to work with typeOrm. We have also installed pg, which helps to connect and communicate with the Postgresql database.

Step 4: Connect the database

  • Let’s add the typeORM module in app.module.ts; here is the code snippet of app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';

@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
password: 'simform',
username: 'postgres',
entities: [],
database: 'pgWithNest',
synchronize: true,
logging: true,
}),
UserModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

Note: make sure you have created database using pgAdmin. you can replace with your credentials along with database name.

You can run the below command to check whether your app is connected to the database or not

npm run start:dev

if you do not get any error then you have successfully connected with database. you can add logging:true in options to debug more.

Step 5: Create Resource (User)

Now, you can create a resource named ‘user’; we will perform CRUD operations for the User; you can use the below command to create the resource.

nest g res user

Select Rest API and type Y to include all the endpoints.

your project files might look like this.

DTO: DTO stands for data transfer object. We can specify the object. (data you can expect from the body in the request).

Entities: Entities are for tables or for collections.

Controller: NestJS controllers handle incoming requests, define API endpoints, and implement business logic to generate appropriate HTTP responses.

Service: NestJS service will perform database operation and return the appropriate response.

Step 6: Install class-validator & class-transformer

You can install class-validator and class-transformer using the below command. class-validator and will help you to validate the request body. you can use class validator decorator while creating DTO for User. To know more about class-validator and class-transformer you can visit here.

npm i --save class-validator class-transformer

Step 7: Create DTO and Entity of the user

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

@Entity()
export class User {
/**
* this decorator will help to auto generate id for the table.
*/
@PrimaryGeneratedColumn()
id: number;

@Column({ type: 'varchar', length: 30 })
name: string;

@Column({ type: 'varchar', length: 15 })
username: string;

@Column({ type: 'varchar', length: 40 })
email: string;

@Column({ type: 'int' })
age: number;

@Column({ type: 'varchar' })
password: string;

@Column({ type: 'enum', enum: ['m', 'f', 'u'] })
/**
* m - male
* f - female
* u - unspecified
*/
gender: string;
}

user/entity/user.entity.ts

user/entity/user.entity.ts

import {
IsAlphanumeric,
IsEmail,
IsEnum,
IsInt,
IsNotEmpty,
IsString,
Matches,
MinLength,
} from 'class-validator';

const passwordRegEx =
/^(?=.*[a-z])(?=.*[A-Z])(?=.*d)(?=.*[@$!%*?&])[A-Za-zd@$!%*?&]{8,20}$/;

export class CreateUserDto {
@IsString()
@MinLength(2, { message: 'Name must have atleast 2 characters.' })
@IsNotEmpty()
name: string;

@IsNotEmpty()
@MinLength(3, { message: 'Username must have atleast 3 characters.' })
@IsAlphanumeric(null, {
message: 'Username does not allow other than alpha numeric chars.',
})
username: string;

@IsNotEmpty()
@IsEmail(null, { message: 'Please provide valid Email.' })
email: string;

@IsInt()
age: number;

@IsString()
@IsEnum(['f', 'm', 'u'])
gender: string;

@IsNotEmpty()
@Matches(passwordRegEx, {
message: `Password must contain Minimum 8 and maximum 20 characters,
at least one uppercase letter,
one lowercase letter,
one number and
one special character`,
})
password: string;
}

user/dto/create-user.dto.ts

Step 8: Update the Service file and controller

Now, as we have defined our entity and DTO as well. Let’s update the service file of the user and perform CRUD operations.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { User } from './entities/user.entity';

@Injectable()
export class UserService {
/**
* Here, we have used data mapper approch for this tutorial that is why we
* injecting repository here. Another approch can be Active records.
*/
constructor(
@InjectRepository(User) private readonly userRepository: Repository<User>,
) {}

/**
* this is function is used to create User in User Entity.
* @param createUserDto this will type of createUserDto in which
* we have defined what are the keys we are expecting from body
* @returns promise of user
*/
createUser(createUserDto: CreateUserDto): Promise<User> {
const user: User = new User();
user.name = createUserDto.name;
user.age = createUserDto.age;
user.email = createUserDto.email;
user.username = createUserDto.username;
user.password = createUserDto.password;
user.gender = createUserDto.gender;
return this.userRepository.save(user);
}

/**
* this function is used to get all the user's list
* @returns promise of array of users
*/
findAllUser(): Promise<User[]> {
return this.userRepository.find();
}

/**
* this function used to get data of use whose id is passed in parameter
* @param id is type of number, which represent the id of user.
* @returns promise of user
*/
viewUser(id: number): Promise<User> {
return this.userRepository.findOneBy({ id });
}

/**
* this function is used to updated specific user whose id is passed in
* parameter along with passed updated data
* @param id is type of number, which represent the id of user.
* @param updateUserDto this is partial type of createUserDto.
* @returns promise of udpate user
*/
updateUser(id: number, updateUserDto: UpdateUserDto): Promise<User> {
const user: User = new User();
user.name = updateUserDto.name;
user.age = updateUserDto.age;
user.email = updateUserDto.email;
user.username = updateUserDto.username;
user.password = updateUserDto.password;
user.id = id;
return this.userRepository.save(user);
}

/**
* this function is used to remove or delete user from database.
* @param id is the type of number, which represent id of user
* @returns nuber of rows deleted or affected
*/
removeUser(id: number): Promise<{ affected?: number }> {
return this.userRepository.delete(id);
}
}

user/user.service.ts

In the above code, we have injected the user repository in the constructor and created a function with createUserDto, which is the request body. Class-validator will validate the request body and discard unnecessary data. If the request body is not as expected or does not pass validations, then the nest will throw an appropriate error.

In the same way, we have updated the viewUser and findAllUser function and updateUser function as well.

Now, we need to update the user.module.ts file. In that file, we will import TypeORMModule and the User entity so we can use it in our user service.

import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';

@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}

Note: we have used static value not env or config module; for this tutorial, we have used static values.

Do not forget to add User Entity in app.module.ts in the entities array.

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
import { AuthModule } from './auth/auth.module';
import { User } from './user/entities/user.entity';

@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
password: 'simform',
username: 'postgres',
entities: [User], // here we have added user enitity in entities array
database: 'pgWithNest',
synchronize: true,
logging: true,
}),
UserModule,
AuthModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

src/app.module.ts

Step 9: Update the service function name in the controller file

import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
} from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

/**
* whatever the string pass in controller decorator it will be appended to
* API URL. to call any API from this controller you need to add prefix which is
* passed in controller decorator.
* in our case our base URL is http://localhost:3000/user
*/
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}

/**
* Post decorator represents method of request as we have used post decorator the method
* of this API will be post.
* so the API URL to create User will be
* POST http://localhost:3000/user
*/
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.userService.createUser(createUserDto);
}

/**
* we have used get decorator to get all the user's list
* so the API URL will be
* GET http://localhost:3000/user
*/
@Get()
findAll() {
return this.userService.findAllUser();
}

/**
* we have used get decorator with id param to get id from request
* so the API URL will be
* GET http://localhost:3000/user/:id
*/
@Get(':id')
findOne(@Param('id') id: string) {
return this.userService.viewUser(+id);
}

/**
* we have used patch decorator with id param to get id from request
* so the API URL will be
* PATCH http://localhost:3000/user/:id
*/
@Patch(':id')
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
return this.userService.updateUser(+id, updateUserDto);
}

/**
* we have used Delete decorator with id param to get id from request
* so the API URL will be
* DELETE http://localhost:3000/user/:id
*/
@Delete(':id')
remove(@Param('id') id: string) {
return this.userService.removeUser(+id);
}
}

user/user.controller.ts

Step 10: Let’s Test an API

we will test one of the APIs from our CRUD operations. we will create one user using createUser API. As mentioned in the above code snippet, our API URL to create a user will be:

POST http://localhost:3000/user

To test an API will use postman. I am attaching a screenshot of the API with the request URL, request data, and response.

Create user API call

Keypoints to use typeOrm with Nestjs:

  • Easy to use: NestJS and TypeORM are both easy to use, even for developers new to Node.js or ORMs. NestJS provides a rich API for interacting with TypeORM, and TypeORM has comprehensive documentation that makes it easy to learn.
  • Automatic schema generation: TypeORM can automatically generate database schemas from your TypeScript entities. This can save you a lot of time and effort.
  • TypeScript support: NestJS and TypeORM are TypeScript-based, meaning you can use TypeScript’s strong typing features. This can help to prevent errors and improve the readability of your code.
  • Large community: NestJS and TypeORM both have large and active communities. This means plenty of resources are available to help you learn about and use these technologies.
  • Flexible: NestJS and TypeORM are very flexible frameworks, which means you can use them to build various applications. NestJS is a full-stack framework that can be used to build web and mobile applications, while TypeORM can be used with any database that supports SQL.
  • Extensible: NestJS and TypeORM are extensible frameworks, meaning you can add new application features and functionality. NestJS has a large ecosystem of third-party modules, while TypeORM has a plugin system that allows you to add new features and functionality to the framework.

Thank you so much for investing your valuable time in reading this article!

Here’s what you can do next:

Give 👏 claps on the article and follow the author for more insightful stories.

Read 📚 more valuable content pieces in Simform Engineering Publication.

Follow Us: Twitter | LinkedIn

--

--