How to create API with NestJS and PostgreSQL
NestJS and PostgreSQL are a good combination to create API.
Hello Guys, In this case, we will learn about NestJS. NestJS is one of the popular frameworks javascript for the backend. We will focus on how to create API with NestJS.
NestJS is a framework for building efficient, scalable Node.js server-side applications. Under the hood, Nest makes use of robust HTTP Server frameworks like Express (the default) and optionally can be configured to use Fastify as well!
The documentation is really enjoyable to understand and complete. Click this link below to read more information.
If you want to create REST API, Graphql, Websockets, Microservice and etc. Absolutely you can use NestJS because all the thing is ready to use and just follow the pattern of NestJs.
The pattern of NestJs looks like an Angular 2+, So, if your background is Frontend Developer (Angular) and then you want to expand your skill to become a Backend Developer with a similar pattern of Angular, this article is right for you.
PostgreSQL is a powerful, open-source object-relational database system that uses and extends the SQL language combined with many features that safely store and scale the most complicated data workloads.
But, I don’t want to talk a lot about PostgreSQL, just for introduction and if you want to read more information you can visit this link below.
In this case, we will use a free database PostgreSQL from https://elephantsql.com/ . So, make sure you have created an account.
Let’s create an API with NestJS together. First of all, I assume that you have installed NodeJs and NPM. In this case, I using nodejs version 16.18.1 and npm version 8.19.2
I suggest you to use NVM to manage a lot version of nodejs and easily to changes the version of nodejs.
Installation
You need to install NestJS CLI. Please use version 9.0.0 of NestJS to make sure that we have the same version to create API with NestJS to avoid an unexpected errors.
npm i -g @nestjs/cli@9.0.0
Init Project
After installation is completed. Let’s initiate the project to run this command.
nest new nestjs-todolist
The view of the project looks like image below
Then, you need to install this package for the utility to create API with PostgreSQL.
npm i --save @nestjs/config @nestjs/typeorm typeorm pg
Configuration Database
Next, on the app.module.ts we need to update like this
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
TypeOrmModule.forRoot({
type: 'postgres',
url: process.env.DATABASE_URL,
autoLoadEntities: true,
synchronize: true,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
- ConfigModule is used when we want to inject some configuration or variable on global, such as JWT_SECRET or something. Then used on another module or service.
- TypeOrmModule is a module to configure the database as you know we are using Postgres. But, we can use another type of database, like mysql, mongo, postgres, etc.
Because we are using the free database from elephantsql.com, we need to create an instance database. So, let’s move on to the elephant.com
Just click on the button to create a new instance.
After that, please fill in the name and choose a free plan. Then, click on the button Select Region.
After that, choose the data center. By default, it will auto-select depends where are you from. Example: when you are from Europe, it will be selected on EU. So, just click on the button Review to the next step.
If all information is valid, just click on the button to create an instance. Just wait a minute. After finishing just click the detail of the instance database. So, the page of detail looks like this image below
After that, copy the URL link of your database instance and paste it on file .env with the variable name DATABASE_URL.
We need to make sure the connection between PostgreSQL and the project is not an error. just run this command
npm run start
If your terminal doesn’t have an error message. So the connection is safe. Like the image bellow
Create API
All the configuration is done. Let’s jump into the main tutorial, and build an API. We need to generate a module, controller, services, class, and interface.
Ah ya, I forget to mention the powerfull CLI NestJS. We can generate a file and look at the image below.
Next, run this command to generate the module, controller, class, interface and services.
npx nest generate module todolist && npx nest generate controller todolist/controllers/todolist && npx nest generate service todolist/services/todolist && npx nest generate interface todolist/interfaces/todolist
After that, we need to create Entity (table) and DTO (data transfer object) to use the database and how to manage them with typeorm function.
Create file entity for todolist with filename todolist.entity.ts on folder src/todolist/entities and add this script
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
export enum StatusTodo {
TODO = 'todo',
PROGRESS = 'progress',
COMPLETED = 'completed',
}
@Entity()
export class TodolistEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
description: string;
@Column({ type: 'enum', default: StatusTodo.TODO, enum: StatusTodo })
status: string;
@Column({ nullable: true, type: 'bigint', default: new Date().getTime() })
due_date: number;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
- We add @Entity to tell the typeform that the class TodolistEntity should generate into the table.
- @PrimaryGeneratedColum is a function to generate an id with auto increment as a primary key.
- @Column is a function to define a field on the table
- The status of todolist is enum StatusTodo with the default value todo.
- The reason why on the due_date we are using the number is to handle different timezone.
You can read more about typeorm in the link below
After that, we need to create DTO (Data Transfer Object) to make sure the property request from the client is available on the backend. Also, for validation, we need to install this library
npm i --save class-validator class-transformer
Create file todolist.dto.ts on folder src/todolist/dtos/ and add this code
import { IsNotEmpty, IsNumber, IsString, Length } from "class-validator";
export class TodolistDto {
@IsNotEmpty()
@IsString()
@Length(10, 20)
title: string;
@IsNotEmpty()
@IsString()
@Length(0, 40)
description: string;
@IsString()
status: string;
@IsNumber()
@IsNotEmpty()
due_date: number;
}
- @IsNotEmpty() is a function to validate the property should not be empty
- @IsString() is a function to validate the value of the property should string
- @Length() is a function to validate minimum and maximum character value
- @IsNumber() is a function to validate the value of the property should the number
Don’t forget to update file main.ts to be able to use validation on the global scope.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
Next, open the file todolist.interface.ts that we have generated with NestJS CLI.
export interface Todolist {
id: number;
title: string;
description: string;
status: string;
due_date: number;
createdAt: Date;
updatedAt: Date;
}
The function of the interface todolist is to add static typing on variables, or responses.
After that, open file todolist.module.ts and import typeorm with a feature from todolist entity.
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TodolistController } from './controllers/todolist/todolist.controller';
import { TodolistEntity } from './entities/todolist.entity';
import { TodolistService } from './services/todolist/todolist.service';
@Module({
imports: [TypeOrmModule.forFeature([TodolistEntity])],
controllers: [TodolistController],
providers: [TodolistService]
})
export class TodolistModule {}
To make sure our configuration is no error, run this command
npm run start
If the application is no error, just check the dashboard elephantsql. The dashboard should contain a table todolist.
Let’s back to IDE to create API. Open file todolist.controller.ts. The function of the controller in NestJS is to handle requests from users, responds to users,s and define routes API.
import {
Body,
Controller,
Delete,
Get,
Param,
ParseIntPipe,
Post,
Put,
} from '@nestjs/common';
import { TodolistDto } from 'src/todolist/dtos/todolist.dto';
import { TodolistService } from 'src/todolist/services/todolist/todolist.service';
@Controller('todolist')
export class TodolistController {
constructor(private readonly todolistService: TodolistService) {}
@Get()
findAll() {
return this.todolistService.findAll();
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.todolistService.findOne(id);
}
@Post()
create(@Body() todolistDto: TodolistDto) {
return this.todolistService.create(todolistDto);
}
@Put(':id')
update(
@Param('id', ParseIntPipe) id: number,
@Body() todolistDto: TodolistDto,
) {
return this.todolistService.update(id, todolistDto);
}
@Delete(':id')
delete(@Param('id', ParseIntPipe) id: number) {
return this.todolistService.delete(id);
}
}
- @Controller is a function to add route grouping some feature, for example: feature todolist
- We need to inject dependency todolist service to manage data from nestjs to the database with typeorm.
- @Param() is a function to catch the path parameter on route API. example: /todolist/:id
- @Body() is a function to get all field requests from clients by DTO
- @GET(), @POST() , @PUT(), @DELETE() is method on HTTP
- ParseIntPipe is a function to convert the id or param to a number.
After that, we need to modify file todolist.service.ts
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { TodolistDto } from 'src/todolist/dtos/todolist.dto';
import { TodolistEntity } from 'src/todolist/entities/todolist.entity';
import { Todolist } from 'src/todolist/interfaces/todolist/todolist.interface';
import { Repository } from 'typeorm';
@Injectable()
export class TodolistService {
constructor(
@InjectRepository(TodolistEntity)
private todolistRepository: Repository<TodolistEntity>,
) {}
async delete(id: number) {
const todolist: Todolist = await this.todolistRepository.findOneBy({ id });
if (!todolist) {
throw new HttpException(
`Todolist with id=${id} not found!`,
HttpStatus.BAD_REQUEST,
);
}
return this.todolistRepository.delete(id);
}
async update(id: number, todolistDto: TodolistDto) {
const todolist: Todolist = await this.todolistRepository.findOneBy({ id });
if (!todolist) {
throw new HttpException(
`Todolist with id=${id} not found!`,
HttpStatus.BAD_REQUEST,
);
}
return this.todolistRepository.update(id, todolistDto);
}
async findOne(id: number) {
const todolist: Todolist = await this.todolistRepository.findOneBy({ id });
if (!todolist) {
throw new HttpException(
`Todolist with id=${id} not found!`,
HttpStatus.BAD_REQUEST,
);
}
return todolist;
}
async findAll() {
return await this.todolistRepository.find();
}
async create(todolistDto: TodolistDto) {
const newTodolist = await this.todolistRepository.create({
...todolistDto,
});
const saveTodolist = await this.todolistRepository.save(newTodolist);
if (!saveTodolist) {
throw new HttpException(`Create todolist failed`, HttpStatus.BAD_REQUEST);
}
return saveTodolist;
}
}
- We need to inject a repository from typeorm with todolist entity to manage data on the database.
- In this case, we are using async await, it means is Promise, but if you want to be Observable of course is possible, you just need to convert it to observable.
- this.todolistRepository is an instance function from typeorm with a lot of methods to manage data on a database, such as find, findOneBy, create, save, update, and delete.
- HttpException is a function from nestjs to throw data when the data is not found or getting some error.
- HttpStatus is a function to define all HTTP status codes like 200, 300, 400, and 500.
- Todolist on type const todolist is for declaring all property from the response.
Until now the progress to build API with NestJS and PostgreSQL is done. The next step is testing API. So, let’s test the API.
Testing API
For testing API, We can use Postman. Before testing, please make sure that the project nestjs was running. You can use this command to auto-rebuild when there are changes from the project.
npm run start:dev
- CREATE
- GET ALL
- GET ONE
- UPDATE
- DELETE
- Validation
- Check From ElephantSQL
Yap, everything is tested and I think we arrived at the last part. So thank you to read this tutorial. If you want to look up this project just visit this link below
Challenge
Just for the exercise, you can try to create an API to update the status of progress todolist, such as todo, progress, and completed. Keep it Spirit!
Closing
Hopefully, this article is useful and improves your skills. Of course, I’m still learning as you know, because when we are long-life learners, we will know that the world is very wide. If you want to contact me, don’t hesitate to contact me at afifalfiano2@gmail.com Thank you.
References
#nestjs #postgresql #backend #tutorial #restapi