Hiểu về Data Transfer Objects (DTO) and Xác thực dữ liệu trong NestJS(TypeScript)

TN Duoc

Đối tượng truyền dữ liệu (DTO) là cơ sở xác thực dữ liệu trong các ứng dụng NestJS. DTO cho phép nhiều lớp xác thực dữ liệu linh hoạt khác nhau trong NestJS.

Trong bài này, tôi sẽ đi sâu vào các đối tượng truyền dữ liệu, thảo luận về cơ chế xác thực, mô hình xác thực và mọi thứ khác mà bạn cần biết về đối tượng truyền dữ liệu(DTO).

Data transfer object

Data Transfer Object là gì?

Một data transfer object,, thường được gọi là DTO, là đối tượng dùng để xác thực dữ liệu và xác định cấu trúc dữ liệu được gửi vào ứng dụng Nest của bạn. DTO tương tự như giao diện, nhưng khác với giao diện ở những điểm sau:

  • Các giao diện được sử dụng để kiểm tra kiểu và định nghĩa cấu trúc.
  • DTO được sử dụng để kiểm tra kiểu, định nghĩa cấu trúc và xác thực dữ liệu.
  • Các giao diện biến mất trong quá trình biên dịch vì nó có nguồn gốc từ TypeScript và không tồn tại trong JavaScript.
  • DTO được xác định bằng cách sử dụng các lớp được hỗ trợ trong JavaScript gốc. Do đó, nó vẫn còn sau khi biên dịch.


Riêng DTO chỉ có thể thực hiện kiểm tra kiểu và định nghĩa cấu trúc. Để chạy xác thực dữ liệu bằng DTO, bạn sẽ cần sử dụng NestJS ValidationPipe.

Cơ chế xác thực

Pipe được sử dụng để xác thực dữ liệu trong NestJS. Dữ liệu đi qua một Pipe được đánh giá, nếu nó vượt qua bài kiểm tra xác thực, nó sẽ được trả về nguyên vẹn; nếu không, một lỗi sẽ được đưa ra.

NestJS có 8 Pipes sẵn có, nhưng bạn sẽ tập trung vào ValidationPipe sử dụng gói class-validator vì nó trừu tượng hóa rất nhiều mã dài dòng và giúp dễ dàng xác thực dữ liệu bằng cách sử dụng decorators.

Tạo DTO trong mô hình xác thực người dùng đơn giản

Mô hình xác thực người dùng là một ví dụ để làm sáng tỏ DTO. Điều này là do nó yêu cầu nhiều cấp độ xác thực dữ liệu. Hãy tạo và khám phá DTO và ValidationPipe để xác thực tất cả các dạng dữ liệu đưa vào ứng dụng của tôi.

Thiết lập môi trường phát triển

Để thiết lập môi trường phát triển của bạn:

Tạo một dự án mới bằng Nest CLI,

  • Tạo Module, Service và Controller của bạn bằng CLI,
  • Cài đặt Mongoose và kết nối ứng dụng của bạn với cơ sở dữ liệu,
  • Tạo thư mục Schema của bạn và xác định Schema.


Một Schema xác thực người dùng thông thường sẽ có dạng như sau:

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

export type UserDocument = User & Document;

@Schema()
export class User {
  @Prop({ required: true })
  fullName: string;

  @Prop({ required: true })
  email: string;

  @Prop({ required: true })
  password: string;
}
export const UserSchema = SchemaFactory.createForClass(User);

Có Tên, email và mật khẩu required

Tạo một thư mục bên trong Module của bạn và gọi nó là dto. Đây là nơi các lớp dto của bạn sẽ được lưu trữ và xuất.
Tạo một tệp trong thư mục dto của bạn và đặt tên là user.dto.ts.

Cấu trúc của 1 DTO

DTO là một lớp(class), vì vậy nó tuân theo cú pháp giống như một class

export class newUserDto {
  fullName: string;
  email: string;
  password: string;
}

Thực hiện xác thực dữ liệu

Để thêm chức năng xác nhận bạn cần làm theo các bước dưới đây.

  • Bên trong main.ts của bạn,
  • Nhập {ValidationPipe} từ ‘@nestjs/common’,
  • Bên trong hàm bootstrap và ngay bên dưới hằng số ứng dụng, hãy gọi phương thức useGlobalPipes trên ứng dụng và chuyển vào ValidationPipe() mới làm đối số.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import * as dotenv from 'dotenv';
dotenv.config();

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(process.env.PORT);
}
bootstrap();

Điều này sẽ cho phép xác thực tự động và đảm bảo rằng tất cả các điểm cuối đều được bảo vệ khỏi việc nhận dữ liệu không chính xác.

  • Cài đặt gói class-validator và class-transformer bằng cách chạy:
npm i --save class-validator class-transformer

Trình class-transformer chuyển đổi các đối tượng JSON thành một phiên bản của lớp DTO của bạn và ngược lại.

Gói trình class-validator có rất nhiều truy vấn xác thực mà bạn có thể tìm thấy trong tài liệu của họ, nhưng bạn sẽ tập trung vào một số truy vấn trong số đó liên quan đến xác thực dữ liệu người dùng. Trong tệp user.dto.ts của bạn, hãy nhập các truy vấn sau từ class-validator

  • IsNotEmpty: Trình xác thực này kiểm tra xem giá trị đã cho có trống hay không. Nó lấy một đối tượng của các tùy chọn làm đối số.
  • IsString: Trình xác thực này kiểm tra xem giá trị đã cho có phải là chuỗi thực hay không. Nó lấy một đối tượng của các tùy chọn làm đối số.
  • IsEmail: Trình xác thực này kiểm tra xem giá trị đã cho có thuộc loại email hay không. Nó lấy một đối tượng của các tùy chọn làm đối số.
  • Độ dài: Trình xác thực này có 3 đối số. Đầu tiên là MinLength, sau đó là MaxLength và cuối cùng là đối tượng của các tùy chọn.

Ở trên sẽ được sử dụng làm decorators để xác thực các trường tương ứng của chúng. Thực hiện điều này trong DTO của bạn,

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

export class newUserDto {
  @IsNotEmpty({ message: 'Please Enter Full Name' })
  @IsString({ message: 'Please Enter Valid Name' })
  fullName: string;

  @IsEmail({ message: 'Please Enter a Valid Email' })
  email: string;

  @Length(6, 50, {
    message: 'Password length Must be between 6 and 50 charcters',
  })
  password: string;
}

Creating a New User

Tiếp theo chúng ta sẽ tạo mới 1 user để áp dụng DTO xem thế nào?

Để tạo người dùng mới có thông tin xác thực hợp lệ, bạn sẽ cần nhập DTO mà bạn đã tạo trước đó vào dịch vụ của mình.

Service Logic

  • Tạo một hàm async newUser nhận tham số user sẽ là dữ liệu của người dùng mới. Đặt loại của nó thành DTO được tạo trước đó.
import { Injectable } from '@nestjs/common';
import { Model } from 'mongoose';
import { InjectModel } from '@nestjs/mongoose';
import { User, UserDocument } from './schemas/user.schema';
import { newUserDto } from './dto/user.dto';

@Injectable()
export class AuthService {
  constructor(
    @InjectModel(User.name)
    private userModel: Model<UserDocument>,
  ) {}

  async newUser(user: newUserDto): Promise<User> {
    return await new this.userModel(user).save();
  }
}

DTO và trình xác thực mà chúng ta thiết lập trước đó sẽ liên tục kiểm tra xem dữ liệu có hợp lệ hay không trước khi lưu trữ dữ liệu người dùng vào cơ sở dữ liệu.

Controller Logic

import {
  Controller,
  Body,
  Post, 
} from '@nestjs/common';
import { AuthService } from './auth.service';
import { newUserDto } from './dto/user.dto';

@Controller('users')
export class AuthController {
  constructor(private readonly service: AuthService) {}

  @Post('signup')
  async createUser(
    @Body()
    user: newUserDto,
  ): Promise<newUserDto> {
    return await this.service.newUser(user);
  }

Như bạn có thể thấy trong khối mã ở trên, tham số người dùng có loại newUserDto. Điều này đảm bảo rằng tất cả dữ liệu vào ứng dụng khớp với DTO, nếu không nó sẽ báo lỗi.

Testing

Kiểm tra việc xác thực bằng một số dữ liệu giả bằng cách gửi yêu cầu tới http://localhost:3000/users/signup bằng PostMan hoặc công cụ khác mà bạn hay sử dụng.

{
    "fullName":"Jon Snow",
    "email":"snow@housestark.com",
    "password":"password"
}

Dữ liệu JSON ở trên thảo mãn tất cả điều kiện. Do đó, bạn sẽ nhận được phản hồi 201 kèm theo dữ liệu, với thuộc tính _id.

Như vậy đã có thể hiểu thế nào data transfer object và cách thức hoạt động của nó.

Phần kết


Đây là bản tóm tắt những gì tôi đã trình bày.

  • DTO là gì
  • Sự khác biệt giữa DTO và interface.
  • Cấu trúc của một DTO
  • Cơ chế xác thực NestJS,

Bạn có thể tìm hiểu thêm tại Document của NestJs
https://docs.nestjs.com/techniques/validation

Bạn cũng có thể tham khảo bài viết về NestJs

XÂY DỰNG API ĐƠN GIẢN VỚI NESTJS

Viết một bình luận