DTO设计模式
DTO,即Data Transfer Object,是一种设计模式,用于在不同的软件系统层之间传输数据。举一个最常见的例子,前端通过Post方法向后端传入数据,后端接收到数据后,将数据存储到数据库中。
在NestJS中,DTO用于以下作用:
- 封装客户端到服务器的数据传输
- 实现服务器端对数据的验证和转化
Nest实现DTO
在NestJS中,实现DTO主要分为三步:
- 定义DTO类
- 使用Pips进行数据验证
- 如果有需要的话,将数据进行转换
定义DTO类
假设我们需要实现用户登录功能,我们将用户登录注册相关的逻辑都放在一个模块里。为此我们通过nest指令先生成一个CRUD模板
nest g res user
在我们生成的这个模板中,就有dto文件夹
在这个文件夹里有两个默认生成的文件,分别是create-user.dto.ts和update-user.dto.ts
我们首先实现添加用户,即注册功能。为此我们需要先在create-user.dto.ts中编写
export class CreateUserDto {
user_name: string
account: string
password: string
}
我们要求注册的时候,需要提供给用户名、账号和密码。
实现了注册相关的DTO类后,我们就可以在service里面去使用:
在生成的user.service.ts里导入DTO类,然后作为参数传入相关的方法里
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
@Injectable()
export class UserService {
create(createUserDto: CreateUserDto) {
return console.log(createUserDto);
}
}
然后我们启动服务,通过Postman向该接口发送Post请求,看看通过DTO处理后的参数,会包含哪些信息
我们在传递数据的时候,还传入了没有在DTO类里定义的数据,如hobby和age。那么后端会输出些什么呢?
我们发现,我们之前写的代码,最终不过是将前端传入的参数按照原样输出了而已,那么DTO有个锤子🔨用呢?不急,我们现在才只是在形式上使用了DTO类而已,还没有接触到DTO的大杀器。
这样我给你举一个例子:
我们建立的数据表只有user_name、account和password三个字段,现在前端给我们传来了五个字段:
user_nameaccountpasswordhobbyage有两个字段我们是用不到的,我们要将传来的数据通过prisma写入数据库中。
如果是你,你会如何解决这个问题?
有的人会说,我通过@Body装饰器,获取到body里面的数据,然后解构出需要的数据不就可以了?
create(@Body body){
let {user_name, accont, password } = body.data.user_name
}
不错👍,是可以
那我如果跟你说,DTO通过配置,可以自动过滤掉无用的字段,即hobby和age不会出现在下面的createUserDto里,是否对DTO有那么小小的心动了?
create(createUserDto: CreateUserDto) {
return console.log(createUserDto);
}
那如果我再跟你说,DTO通过配置,可以检验前端传来的字段,如果类型错误会返回错误信息给前端。例如: 假设前端发送JSON对象
{
"user_name": 123,
"account": "IcicleCrino",
"password": "myPassword"
}
在这里user_name不是字符串,通过DTO配置后,服务器返回
{
"statusCode": 400,
"message": [
"user_name must be a string"
],
"error": "Bad Request"
}
在返回的报错信息里还包括提示。现在你对DTO是不是更加心动了?
心动不如行动,我们现在就开始学习如何配置
检验数据类型
检验传递给服务器的字段格式的逻辑,在相关的DTO类中编写,这里我们要配置注册DTO类,所以我们在create-user.dto.ts里编写。
对格式的检验是通过Nest提供的装饰器完成,在使用之前,我们需要安装相应的库
npm install class-validator class-transformer
安装完成后,我们还需要应用ValidationPipe来自动验证传入的数据。我们在user.controlelr.ts中添加
import { Controller, Post, Body, } from '@nestjs/common';
import { UserService } from './user.service';
//UsePipes装饰器,用于在类或方法应用管道
//ValidationPipe是NestJS提供的内置管道,用于实现自动验证
import { UsePipes, ValidationPipe } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) { }
@Post()
//传入ValidationPipe实例
@UsePipes(new ValidationPipe({
//将请求体中的数据转化为DTO类的实例
transform: true,
//只接受DTO中定义的属性,忽略其他属性
whitelist: true,
}))
create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
}
由于我们开启了白名单,前端传递的参数中,如果在DTO类中没有定义,则会被过滤,还是拿之前的例子:
前端传来了两个无效参数
此时后端接收到的对象中,已经去除了无效参数
然后我们在DTO类中编写类型检验,通过内置装饰器去检验类型:
import { IsString, IsNotEmpty } from 'class-validator';
export class CreateUserDto {
@IsNotEmpty()
@IsString()
user_name: string;
@IsNotEmpty()
@IsString()
account: string;
@IsNotEmpty()
@IsString()
password: string;
}
在这里,我么定义了字段的值必须是string,并且是非空的。
现在我们通过Postman调用接口,将用户名改成数字123
可以发现,后端返回了错误信息给前端。到这里我们就在NestJS中深入使用了DTO类。