DTO 数据传输对象
DTO是"数据传输对象"(Data Transfer Object)的缩写,它是一种设计模式,通常用于应用程序的不同层之间传输数据。
简单概念入门
如果没有 DTO 就需要在 controller 中重复写 @Rep() 等等的代码
import { Controller, Post, Req } from '@nestjs/common';
import { AuthService } from './auth.service';
import { Request } from 'express';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Post('signup')
signup(@Req() req: Request) {
console.log(req.body);
return this.authService.signup();
}
}
创建 DTO 文件
在 module 文件夹下创建 dto
./src/auth
├── auth.controller.ts
├── auth.module.ts
├── auth.service.ts
└── dto
├── auth.dto.ts
└── index.ts
auth.dto.ts
export interface AuthDto {
email: string;
password: string;
}
index.ts
export * from './auth.dto';
提示
建议在 typescript 中的文件模块使用 index.ts 来导出子模块
在 controller 中使用
import { Body, Controller, Post } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthDto } from './dto';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Post('signin')
signin(@Body() dto: AuthDto) {
console.log({ dto });
return this.authService.signin();
}
}
使用 pipes 校验数据
- dto
- 使用 pipes 检验字段
一、单字段检验
代码示例
// controller
{
...
@Post('signin')
signin2(
@Body('email') email: string,
@Body('password') password: string,
@Body('age', ParseIntPipe) age: number,
) {
console.log({
email,
typeOfEmail: typeof email,
password,
typeOfPassword: typeof password,
age,
typeOfAge: typeof age,
});
return this.authService.signin();
}
}
// http://localhost:3333/auth/signin
{
"email": "306624995@qq.com",
"password": "test",
"age": 18
}
存入正确信息后,控制台输出
{
email: '306624995@qq.com',
typeOfEmail: 'string',
password: 'test',
typeOfPassword: 'string',
age: 18,
typeOfAge: 'number'
}
输入年龄错误时,则不继续逻辑运算,直接返回错误信息给客户端
{
"message": "Validation failed (numeric string is expected)",
"error": "Bad Request",
"statusCode": 400
}
二、使用 Class validator
npm i --save class-validator class-transformer
// dto 文件
import { IsEmail, IsString, IsNotEmpty } from 'class-validator';
export class AuthDto {
@IsEmail()
@IsNotEmpty()
email: string
@IsString()
@IsNotEmpty()
password: string
}
在全局注册 ValidationPipe
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3333);
}
bootstrap();
测试效果
{
"email": "",
"password": 1
}
{
"message": [
"email should not be empty",
"email must be an email",
"password must be a string"
],
"error": "Bad Request",
"statusCode": 400
}
注意
存在问题,假如入参多传参数。只需要在 ValidationPipe 实例化时加上{whitelist: true} 即可
...
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
whitelist: true
}));
await app.listen(3333);
}
bootstrap();
密码校验
// https://github.com/ranisalt/node-argon2
pnpm i argon2
- 主要作用创建 hash 密码,并保存在数据库中
// auth.service.ts
export class AuthService {
constructor(private prisma: PrismaService) {}
async signup(dto: AuthDto) {
// generate the password hash value
const hash = await argon.hash(dto.password);
// save the new user in the db
const user = await this.prisma.user.create({
data: {
email: dto.email,
hash,
},
// 1. Select return Object
// select: {
// email:true,
// hash: false,
// ...
// },
});
// 2. The hash of user is not returned
delete user.hash;
return user;
}
...
}
数据库联表
将 user 表的 id 与 bookmark 中进行关联
model User {
id Int @id @default(autoincrement())
createAt DateTime @default(now())
updateAt DateTime @updatedAt
email String @unique
hash String
firstName String?
lastName String?
bookmarks Bookmark[]
@@map("users")
}
model Bookmark {
id Int @id @default(autoincrement())
createAt DateTime @default(now())
updateAt DateTime @updatedAt
title String
link String
description String?
userId Int
user User @relation(fields: [userId], references: [id])
@@map("bookmarks")
}
npx prisma migrate dev
prisma 错误捕获
由于 prisma 中的 email 字段添加 @unique 的字段,重复注册将报错。
控制台报错
Unique constraint failed on the fields: (`email`)
客服端返回
{
"statusCode": 500,
"message": "Internal server error"
}
信息
使用 try catch 捕获,详细错误码可点击 check prisma error-codes
// auth.service.ts
export class AuthService {
constructor(private prisma: PrismaService) {}
async signup(dto: AuthDto) {
// generate the password hash value
const hash = await argon.hash(dto.password);
try {
// save the new user in the db
const user = await this.prisma.user.create({
data: {
email: dto.email,
hash,
}
});
delete user.hash;
return user;
} catch (error) {
if (error instanceof PrismaClientKnownRequestError) {
if (error.code === 'P2002') {
throw new ForbiddenException(
`Unique consstraint failed on the ${error.meta?.target}`,
);
}
}
throw error;
}
}
...
}