1.Giới thiệu

NestJS là một framework Node.JS cho phép xây dựng ứng dụng phía server. Nest mở rộng các framework Node.js như Express hay Fastify để bổ sung thêm nhiều module hay thư viện hỗ trợ việc xử lý tác vụ. Đây là một framework mã nguồn mở, sử dụng TypeScript và rất linh hoạt để xây dựng các hệ thống backend.

2.Tạo một dự án

Môi trường cần chuẩn bị

Node Yarn Nestjs MySQL
v16.16.0 1.22.19 9.0.0 8.0.26

Cài đặt Nest CLI để tạo một project bằng hai lệnh dưới đây:

yarn global add @nestjs/cli
nest new todos

Sau khi chạy 2 lệnh trên ta sẽ có một source code với cấu trúc như sau:

Ta chỉ cần để ý đến thư mục src với ba file được tạo sẵn:

  • main.ts: File để khởi tạo các đối tượng chạy ứng dụng, chẳng hạn ta có thể dùng lệnh NestFactory.create() để tạo instance cho Nest.
  • app.module.ts: Là module gốc của ứng dụng, có trách nhiệm đóng gói mọi thứ có trong project.
  • app.controller.ts: Chứa các router để xử lý các request và trả về response cho client.
  • app.services.ts: Chứa các hàm xử lý logic cho service, chẳng hạn như ứng dụng có service kết nối đến DB hoặc xử lý file,…
  • app.controller.spec.ts: File dùng để viết unit test cho các controller.

Nhìn chung thì có ba thành phần chính trong NestJS: controller, provider và module.

main.ts sẽ sử dụng static method create() của NestFactory để tạo server app như sau:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

kiểm tra hoạt động ban đầu

cd todos
yarn start:dev

Truy cập:  http://localhost:3000

3.Cài đặt TypeORM và DB

cd todos
yarn add @nestjs/typeorm typeorm mysql class-transformer class-validator
yarn add @nestjs/typeorm typeorm mysql2 reflect-metadata
yarn add -D typeorm-extension

Tạo ba File cấu hình.

src / config / ormconfig.ts

import { DataSourceOptions } from 'typeorm';

const ormconfig: DataSourceOptions = {
  name: 'default',
  type: 'mysql',
  host: process.env.DB_HOST,
  port: parseInt(process.env.DB_PORT, 10),
  username: process.env.DB_USERNAME,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_DATABASE,
  synchronize: false,
  logging: false,
  connectTimeout: 30 * 1000,
  entities: [process.cwd() + '/dist/**/entities/**/*.entity.js'],
  migrations: [process.cwd() + '/dist/database/migrations/**/*.js'],
  charset: 'utf8mb4_general_ci',
};
export default ormconfig;

src / config / ormdatasource.ts

import { DataSource } from 'typeorm';
import ormconfig from './ormconfig';

export const AppDataSource = new DataSource(ormconfig);

.env

# DB
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=1234
DB_DATABASE=todolist

4.Table định nghĩa

Column_Name Type
task_id integer
title varchar
due_date date
status tinyint
created_at datetime
updated_at datetime
-- todolist.task definition

CREATE TABLE `task` (
  `task_id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(20) NOT NULL,
  `due_date` date NOT NULL,
  `status` tinyint(4) DEFAULT '1',
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`task_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

5.Tạo entity

cd todos
mkdir src/entities
touch $_/task.entity.ts

update code vào task.entity.ts

import {
    Entity,
    Column,
    PrimaryGeneratedColumn,
    CreateDateColumn,
    UpdateDateColumn,
  } from 'typeorm';
  
  @Entity()
  export class Task {
    @PrimaryGeneratedColumn()
    readonly task_id: number;
  
    @Column('varchar', { length: 20, nullable: false })
    title: string;
  
    @Column('date', { nullable: false })
    due_date: Date;
  
    @Column('tinyint', { width: 1, default: 1 })
    status: number;
  
    @CreateDateColumn()
    readonly created_at?: Date;
  
    @UpdateDateColumn()
    readonly updated_at?: Date;
  }

6.Tạo Controller

Khi có request HTTP đến, cơ chế routing sẽ chuyển request này đến controller tương ứng để xử lý và trả về phản hồi thích hợp. Để tạo một controller thì ta dùng @Controller() để liên kết class Controller với request tương ứng.

Sử dụng câu lệnh để Nest CLI tạo tự động:

yarn nest g controller task

src/task/task.controller.tsTệp được tạo có nội dung:

import { Controller } from '@nestjs/common';

@Controller('task')
export class TaskController{}

Tất nhiên, đây chỉ là một controller rỗng, chưa có làm gì cả.
Sau khi implement logic create, read, update, delete vào. Thực hiện gọi TaskService

import { Controller, Get, Post, Put, Delete, Body, Param } from '@nestjs/common';
import { TaskService } from './task.service';
import { Task } from '../entities/task.entity';
import { CreateTaskDTO, UpdateTaskDTO } from './task.dto';
import { InsertResult, UpdateResult, DeleteResult } from 'typeorm';

@Controller('task')
export class TaskController {
 constructor(private readonly service: TaskService) {}

 @Get()
 async getTaskList(): Promise<Task[]> {
   return await this.service.findAll();
 }

 @Post()
 async addTask(@Body() task: CreateTaskDTO): Promise<InsertResult> {
   return await this.service.create(task);
 }

 @Get(':id')
 async getTask(@Param('id') id: string): Promise<Task> {
   return await this.service.find(Number(id));
 }

 @Put(':id/update')
  async update(
    @Param('id') id: string,
    @Body() task: UpdateTaskDTO,
  ): Promise<UpdateResult> {
    return await this.service.update(Number(id), task);
  }

  @Delete(':id')
  async deleteTask(@Param('id') id: string): Promise<DeleteResult> {
    return await this.service.delete(Number(id));
  }
}

7.Tạo Service

yarn nest g service task

src/task/task.service.tsTệp được tạo.

Sau khi implement logic create, read, update, delete vào.

import { Injectable } from '@nestjs/common';
import { Task } from 'src/entities/task.entity';
import { Repository, InsertResult, UpdateResult, DeleteResult } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { CreateTaskDTO } from './task.dto';

@Injectable()
export class TaskService {
  constructor(
    @InjectRepository(Task)
    private readonly taskRepository: Repository<Task>
  ) {}

 async findAll(): Promise<Task[]> {
   return await this.taskRepository.find();
 }

 async create(Task: CreateTaskDTO): Promise<InsertResult> {
   return await this.taskRepository.insert(Task);
 }

 async find(id: number): Promise<Task> | null {
   return await this.taskRepository.findOne({where:{ task_id: id }});
 }

 async update(id: number, Task): Promise<UpdateResult> {
    return await this.taskRepository.update(id, Task);
  }

  async delete(id: number): Promise<DeleteResult> {
    return await this.taskRepository.delete(id);
  }
}

8.Tạo Module

Một module được thiết kế để đóng gói các logic liên quan của những chức năng cần triển khai đến client một cách độc lập. Một module trong Nest là class được định nghĩa với decorator @Module() của Nest, dùng để mô tả các thuộc tính như controller, provider hay dependency của module này.

Tạo một Mô-đun bằng lệnh sau.

yarn nest g module task

src/task/task.module.tsSẽ được tạo, sau chỉnh sửa

import { Module } from '@nestjs/common';
import { TaskController } from './task.controller';
import { TaskService } from './task.service';
import { Task } from 'src/entities/task.entity';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  controllers: [TaskController],
  imports: [TypeOrmModule.forFeature([Task])],
  providers: [TaskService],
})
export class TaskModule {}

9.Tạo DTO

touch src/task/task.dto.ts

src / task / task.dto.ts

import { IsNotEmpty, IsString, IsOptional } from 'class-validator';

export class CreateTaskDTO {
  @IsNotEmpty()
  @IsString()
  title: string;

  @IsNotEmpty()
  @IsString()
  due_date: string;
}

export class UpdateTaskDTO {
    @IsOptional()
    @IsNotEmpty()
    @IsString()
    title: string;
  
    @IsOptional()
    @IsNotEmpty()
    @IsString()
    due_date: string;
  }

Sau đó, update code main.ts để kích hoạt validator trong DTO.

src / main.ts

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();

@IsNotEmpty()@IsString()dùng để validator dữ liệu.
Giải thích chi tiết tham khảo :
https://www.npmjs.com/package/class-validator

10.Kiểm tra hoạt động

yarn start:dev

Truy cập:  http://localhost:3000/task

Màn hình trên trình duyệt

[]

Sau đó, chèn dữ liệu mới bằng curl

curl http://localhost:3000/task -X POST -d "title=nestjs&due_date=2022-08-25"

Kết quả thực thi

{"identifiers":[{"task_id":1}],"generatedMaps":[{"task_id":1,"status":1,"created_at":"2022-08-26T13:04:14.550Z","updated_at":"2022-08-26T13:04:14.550Z"}],"raw":{"fieldCount":0,"affectedRows":1,"insertId":1,"serverStatus":2,"warningCount":0,"message":"","protocol41":true,"changedRows":0}}

Truy cập:  http://localhost:3000/task/1

Sau đó, chỉ dữ liệu với id 1 được hiển thị.

[{"task_id":1,"title":"nestjs","due_date":"2022-08-25","status":1,"created_at":"2022-08-26T13:07:29.018Z","updated_at":"2022-08-26T13:07:29.018Z"}]

Tương tự kiểm tra Update, Delete

curl http://localhost:3000/task/1/update -X PUT -d "title=nestjs update"
curl http://localhost:3000/task/1 -X DELETE

Full Source code : https://github.com/lenhht/todos

11.Kết luận

Như vậy chúng ta đã biết được cách dùng framework nestjs cơ bản.
Bài tiếp theo sẽ tìm hiểu về middleware, cache, microservice, run batch trong nestjs

12.Tài liệu tham khảo

https://docs.nestjs.com/
https://typeorm.io/