校验
验证任何发送到 Web 应用程序的数据的正确性是最佳实践。 为了自动验证传入的请求,Nest 提供了几个开箱即用的管道:
ValidationPipe
ParseIntPipe
ParseBoolPipe
ParseArrayPipe
ParseUUIDPipe
ValidationPipe
利用了强大的 class-validator
包及其声明性验证装饰器。
ValidationPipe
提供了一种方便的方法,通过在每个模块中的本地类/DTO 声明中使用简单的注解来强制执行所有传入客户端负载的验证规则。
概述
在 Pipes 章节中,
我们经过了构建简单管道并将它们绑定到控制器、方法或全局应用程序的过程,
以演示该过程的工作原理。确保查阅该章节以更好地理解本章的主题。
在这里,我们将专注于 ValidationPipe
的各种实际用例,
并展示如何使用其一些高级自定义功能。
使用内置的 ValidationPipe
要开始使用它,我们首先安装所需的依赖项。
npm i --save class-validator class-transformer
ValidationPipe
是从 @nestjs/common
包导出的。
因为这个管道使用了 class-validator
和 class-transformer
库,所以有很多可用的选项。
您可以通过传递给管道的配置对象来配置这些设置。
以下是内置选项:
export interface ValidationPipeOptions extends ValidatorOptions {
transform?: boolean;
disableErrorMessages?: boolean;
exceptionFactory?: (errors: ValidationError[]) => any;
}
除了这些选项外,还可以使用从 ValidatorOptions
接口继承的所有 class-validator
选项:
Option | Type | Description |
---|---|---|
enableDebugMessages | boolean | 如果设置为 true ,当出现问题时,验证器将向控制台打印额外的警告消息。 |
skipUndefinedProperties | boolean | 如果设置为 true ,则验证器将跳过验证对象中未定义的所有属性的验证。 |
skipNullProperties | boolean | 如果设置为 true ,则验证器将跳过验证对象中所有为 null 的属性的验证 |
skipMissingProperties | boolean | 如果设置为 true ,则验证器将跳过验证对象中所有为 null 或未定义的属性的验证。 |
whitelist | boolean | 如果设置为 true ,验证器将删除已验证(返回)对象的任何不使用任何验证装饰器的属性。 |
forbidNonWhitelisted | boolean | 如果设置为 true ,验证器将抛出异常,而不是剥离非白名单属性。 |
forbidUnknownValues | boolean | 如果设置为 true ,尝试验证未知对象会立即失败。 |
disableErrorMessages | boolean | 如果设置为 true ,验证错误将不会返回给客户端。 |
errorHttpStatusCode | number | 此设置允许您指定在发生错误时将使用哪种异常类型。默认情况下它会抛出 BadRequestException |
exceptionFactory | Function | 获取验证错误数组并返回要抛出的异常对象 |
groups | string[] | 验证对象期间要使用的组。 |
always | boolean | 为装饰器的always 选项设置默认值。可以在装饰器选项中覆盖默认值 |
strictGroups | boolean | 如果未给出 groups 或 groups 为空,则忽略至少有一组的装饰器 |
dismissDefaultMessages | boolean | 如果设置为 true,验证将不会使用默认消息。如果未明确设置,错误消息始终是undefined 。 |
validationError.target | boolean | 指示目标是否应在 ValidationError 中公开 |
validationError.value | boolean | 指示是否应在 ValidationError 中公开经过验证的值。 |
stopAtFirstError | boolean | 当设置为 true 时,给定属性的验证将在遇到第一个错误后停止。默认为 false 。 |
在其存储库中查找有关class-validator
包的更多信息。
自动校验
我们将从在应用程序级别绑定 ValidationPipe
开始,从而确保所有端点都免受接收错误数据的影响。
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
为了测试我们的管道,让我们创建一个基本端点(endpoint)。
@Post()
create(@Body() createUserDto: CreateUserDto) {
return 'This action adds a new user';
}
由于 TypeScript 不存储有关泛型或接口的元数据,
因此当您在 DTO 中使用它们时,ValidationPipe
可能无法正确验证传入数据。
因此,请考虑在 DTO 中使用具体类。
导入 DTO 时,不能使用仅类型导入,因为这会在运行时被删除,
即记住 import { CreateUserDto }
而不是import type { CreateUserDto }
。
现在,我们可以在 CreateUserDto
中添加一些验证规则。
我们可以使用由 class-validator
包提供的装饰器来实现这一功能,详细说明请参见此处。
这样,任何使用 CreateUserDto
的路由都将自动执行这些验证规则。
import { IsEmail, IsNotEmpty } from 'class-validator';
export class CreateUserDto {
@IsEmail()
email: string;
@IsNotEmpty()
password: string;
}
制定这些规则后,如果请求到达我们的端点且请求正文中的电子email
属性无效,
则应用程序将自动使用 400 Bad Request
代码以及以下响应正文进行响应:
{
"statusCode": 400,
"error": "Bad Request",
"message": ["email must be an email"]
}
除了验证请求体外,ValidationPipe
还可用于其他请求对象属性。
假设我们希望在端点路径中接受 :id
。
为确保该请求参数只接受数字,我们可以使用以下结构:
@Get(':id')
findOne(@Param() params: FindOneParams) {
return 'This action returns a user';
}
FindOneParams
与 DTO 类似,只是一个使用class-validator
定义验证规则的类。
它看起来像这样:
import { IsNumberString } from 'class-validator';
export class FindOneParams {
@IsNumberString()
id: number;
}
禁用详细错误
错误消息有助于解释请求中的错误内容。但是,某些生产环境更喜欢禁用详细错误。
通过将选项对象传递给 ValidationPipe
来完成此操作:
app.useGlobalPipes(
new ValidationPipe({
disableErrorMessages: true,
}),
);
因此,详细的错误消息不会显示在响应正文中。
剥离属性
我们的 ValidationPipe
还可以过滤掉不应由方法处理程序接收的属性。
在这种情况下,我们可以列出可接受的属性,不包括在白名单中的任何属性都会自动从生成的对象中删除。
例如,如果我们的处理程序期望 email
和 password
属性,但请求还包括一个 age
属性,
该属性可以自动从生成的 DTO 中删除。要启用这样的行为,请将 whitelist
设置为 true
。
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
}),
);
当设置为 true
时,这将自动删除非白名单中的属性(那些在验证类中没有任何装饰器的属性)。
或者,您还可以在存在非白名单属性时阻止请求处理,并向用户返回错误响应。
为此,请将 forbidNonWhitelisted
选项属性设置为 true
,并将 whitelist
设置为 true
。
转换有效载荷对象
通过网络输入的有效载荷是纯 JavaScript 对象。
ValidationPipe
可以自动将有效载荷转换为根据其 DTO 类键入的对象。
要启用自动转换,请将 transform
设为 true
。这可以在方法级别完成:
@Post()
@UsePipes(new ValidationPipe({ transform: true }))
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
要全局启用此行为,请在全局管道上设置该选项:
app.useGlobalPipes(
new ValidationPipe({
transform: true,
}),
);
启用自动转换选项后,ValidationPipe
还将执行基本类型的转换。
在以下示例中,findOne()
方法采用一个参数,该参数表示提取的 id
路径参数:
@Get(':id')
findOne(@Param('id') id: number) {
console.log(typeof id === 'number'); // true
return 'This action returns a user';
}
默认情况下,每个路径参数和查询参数都以string
形式通过网络传送。
在上例中,我们指定 id
类型为number
(在方法签名中)。
因此,ValidationPipe
会尝试自动将字符串标识符转换为数字。
显式转换
在上一节中,我们展示了 ValidationPipe
如何根据预期类型隐式转换查询和路径参数。
不过,这一功能需要启用自动转换功能。
或者(在禁用自动转换的情况下),
可以使用 ParseIntPipe
或 ParseBoolPipe
显式转换值(注意不需要 ParseStringPipe
,
因为如前所述,默认情况下,每个路径参数和查询参数都是以string
形式通过网络传送的)
@Get(':id')
findOne(
@Param('id', ParseIntPipe) id: number,
@Query('sort', ParseBoolPipe) sort: boolean,
) {
console.log(typeof id === 'number'); // true
console.log(typeof sort === 'boolean'); // true
return 'This action returns a user';
}
ParseIntPipe
和 ParseBoolPipe
是从 @nestjs/common
包导出的。
映射类型
当您构建 CRUD(创建/读取/更新/删除)等功能时,在基本实体类型上构造变体通常很有用。 Nest 提供了几个执行类型转换的实用函数,使此任务更加方便。
构建输入验证类型(也称为 DTO)时,在同一类型上构建创建和更新变体通常很有用。 例如,创建变体可能需要所有字段,而更新变体可能使所有字段可选。
Nest 提供了 PartialType()
实用程序,使这项工作变得更容易,并最大限度地减少了模板。
PartialType()
函数返回一个类型(类),输入类型的所有属性都设置为可选。
例如,假设我们有一个如下的创建类型:
export class CreateCatDto {
name: string;
age: number;
breed: string;
}
默认情况下,所有这些字段都是必需的。
要创建具有相同字段但每个字段都是可选的类型,请使用 PartialType()
传递类引用 (CreateCatDto
) 作为参数:
export class UpdateCatDto extends PartialType(CreateCatDto) {}
PartialType()
函数是从 @nestjs/mapped-types
包中导入的。
PickType()
函数通过从输入类型中选取一组属性来构造新类型(类)。例如,假设我们从以下类型开始:
export class CreateCatDto {
name: string;
age: number;
breed: string;
}
我们可以使用 PickType()
实用函数从此类中选取一组属性:
export class UpdateCatAgeDto extends PickType(CreateCatDto, ['age'] as const) {}
PickType()
函数是从 @nestjs/mapped-types
包导入的。
OmitType()
函数通过从输入类型中选取所有属性,然后删除一组特定的键来构造类型。
例如,假设我们从以下类型开始:
export class CreateCatDto {
name: string;
age: number;
breed: string;
}
我们可以生成一个具有除 name
之外的所有属性的派生类型,如下所示。
在此构造中,OmitType
的第二个参数是属性名称数组。
export class UpdateCatDto extends OmitType(CreateCatDto, ['name'] as const) {}
OmitType()
函数是从 @nestjs/mapped-types
包导入的。
IntersectionType()
函数将两种类型组合成一种新类型(类)。
例如,假设我们从两种类型开始:
export class CreateCatDto {
name: string;
breed: string;
}
export class AdditionalCatInfo {
color: string;
}
我们可以生成一个结合了这两种类型的所有属性的新类型。
export class UpdateCatDto extends IntersectionType(
CreateCatDto,
AdditionalCatInfo,
) {}
IntersectionType()
函数是从 @nestjs/mapped-types
包导入的。
类型映射实用函数是可组合的。
例如,以下代码将生成一个类型(类),该类型具有 CreateCatDto
类型除name
之外的所有属性,
并且这些属性将设置为可选:
export class UpdateCatDto extends PartialType(
OmitType(CreateCatDto, ['name'] as const),
) {}
解析和验证数组
TypeScript 不存储泛型或接口的元数据。
TypeScript 不存储有关泛型或接口的元数据,因此当您在 DTO 中使用它们时,
ValidationPipe
可能无法正确验证传入的数据。
例如,在下面的代码中,createUserDtos
不会被正确验证:
@Post()
createBulk(@Body() createUserDtos: CreateUserDto[]) {
return 'This action adds new users';
}
要验证数组,请创建一个包含包装数组的属性的专用类,或使用 ParseArrayPipe
@Post()
createBulk(
@Body(new ParseArrayPipe({ items: CreateUserDto }))
createUserDtos: CreateUserDto[],
) {
return 'This action adds new users';
}
此外,ParseArrayPipe
在解析查询参数时可能会派上用场。
让我们考虑一个 findByIds()
方法,该方法根据作为查询参数传递的标识符返回用户。
@Get()
findByIds(
@Query('ids', new ParseArrayPipe({ items: Number, separator: ',' }))
ids: number[],
) {
return 'This action returns users by ids';
}
此构造验证来自 HTTPGET
请求的传入查询参数,如下所示:
GET /?ids=1,2,3
WebSockets 和微服务
本章展示了使用 HTTP 风格应用程序(如 Express 或 Fastify)的示例,
但无论使用哪种传输方法,ValidationPipe
对 WebSockets 和微服务的作用都是一样的。
了解更多信息
点击此处阅读有关class-validator
包提供的自定义验证器、错误信息和可用装饰器的更多信息。