验证发送到 Web 应用程序的任何数据的正确性是最佳实践。为了自动验证传入请求,Nest 提供了几个开箱即用的管道:
ValidationPipeParseIntPipeParseBoolPipeParseArrayPipeParseUUIDPipeValidationPipe 利用了强大的 class-validator 包及其声明式验证装饰器。ValidationPipe 提供了一种便捷的方法来强制执行所有传入客户端负载的验证规则,其中特定规则通过每个模块中本地类/DTO 声明中的简单注解来声明。
在 Pipes 章节中,我们介绍了构建简单管道并将其绑定到控制器、方法或全局应用程序的过程,以演示其工作原理。请务必查看该章节以更好地理解本章内容。在这里,我们将重点介绍 ValidationPipe 的各种实际应用场景,并展示如何使用它的一些高级定制功能。
要开始使用它,我们首先需要安装所需的依赖项。
ValidationPipe 是从 @nestjs/common 包中导出的。
由于该管道使用了 class-validator 和 class-transformer 库,因此有许多可用选项。您可以通过传递给管道的配置对象来配置这些设置。以下是内置选项:
除此之外,所有 class-validator 选项(继承自 ValidatorOptions 接口)都可用:
| 选项 | 类型 | 描述 |
|---|---|---|
| enableDebugMessages | boolean | 如果设为 true,当出现问题时验证器会在控制台打印额外的警告信息。 |
| skipUndefinedProperties | boolean | 如果设为 true,验证器将跳过验证对象中所有未定义的属性。 |
| skipNullProperties | boolean | 如果设为 true,验证器将跳过验证对象中所有为 null 的属性。 |
| skipMissingProperties | boolean | 如果设置为 true,则验证器将跳过验证对象中所有为 null 或 undefined 的属性。 |
| 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 或为空,则忽略至少包含一个组的装饰器。 |
| dismissDefaultMessages | boolean | 如果设为 true,验证将不使用默认消息。若未显式设置,错误消息将始终为 undefined。 |
| validationError.target | boolean | 指示是否应在 ValidationError 中暴露目标对象。 |
| validationError.value | boolean | 指示是否应将验证值暴露在 ValidationError 中。 |
| stopAtFirstError | boolean | 当设置为 true 时,给定属性的验证将在遇到第一个错误后停止。默认为 false。 |
更多关于 class-validator 包的信息请参阅其代码库 。
我们首先将在应用级别绑定 ValidationPipe,从而确保所有端点都受到保护,不会接收错误数据。
为了测试我们的管道,让我们创建一个基本端点。
由于 TypeScript 不会存储关于泛型或接口的元数据,当你在 DTO 中使用它们时,ValidationPipe 可能无法正确验证传入数据。因此,请考虑在 DTO 中使用具体类。
导入 DTO 时,不能使用仅类型导入,因为这在运行时会被擦除,即记得使用 import { CreateUserDto } 而不是 import type { CreateUserDto } 。
现在我们可以在 CreateUserDto 中添加一些验证规则。我们使用 class-validator 包提供的装饰器来实现这一点,具体描述见此处 。通过这种方式,任何使用 CreateUserDto 的路由都会自动执行这些验证规则。
有了这些规则后,如果请求到达我们的端点时请求体中的 email 属性无效,应用程序会自动返回 400 Bad Request 状态码,并附带以下响应体:
除了验证请求体外,ValidationPipe 还可以与其他请求对象属性一起使用。假设我们希望在端点路径中接受 :id 参数。为了确保只接受数字作为此请求参数,我们可以使用以下结构:
FindOneParams 就像一个 DTO,它只是一个使用 class-validator 定义验证规则的类。其结构如下:
错误信息有助于解释请求中的错误,但在某些生产环境中,建议禁用详细错误。可以通过向 ValidationPipe 传递一个选项对象来实现:
这样处理后,响应体中就不会显示详细的错误信息。
我们的 ValidationPipe 还能过滤掉不应被方法处理器接收的属性。此时,我们可以将可接受的属性加入白名单 ,任何未包含在白名单中的属性都会自动从结果对象中剔除。例如,如果处理器需要 email 和 password 属性,但请求中还包含 age 属性,该属性就会自动从最终 DTO 中移除。要启用此行为,需将 whitelist 设为 true。
当设置为 true 时,这将自动移除非白名单属性(即验证类中没有任何装饰器的属性)。
或者,您可以选择在出现非白名单属性时终止请求处理,并向用户返回错误响应。要启用此功能,需将 forbidNonWhitelisted 选项属性设为 true,同时将 whitelist 设为 true。
通过网络传输的有效载荷是纯 JavaScript 对象。ValidationPipe 可以自动将这些有效载荷转换为根据其 DTO 类定义类型的对象。要启用自动转换功能,需将 transform 设置为 true。这可以在方法级别进行配置:
要全局启用此行为,可在全局管道上设置该选项:
当启用自动转换选项后,ValidationPipe 还会执行基本类型的转换。在以下示例中,findOne() 方法接收一个表示提取的路径参数 id 的参数:
默认情况下,每个路径参数和查询参数在网络传输时都是 string 类型。在上例中,我们将 id 类型指定为 number(在方法签名中)。因此,ValidationPipe 会尝试将字符串标识符自动转换为数字。
在上节中,我们展示了 ValidationPipe 如何根据预期类型隐式转换查询和路径参数。但此功能需要启用自动转换。
另一种方式(禁用自动转换时),您可以使用 ParseIntPipe 或 ParseBoolPipe 显式转换值(注意不需要 ParseStringPipe,因为如前所述,默认情况下每个路径参数和查询参数在网络传输时都是 string 类型)。
ParseIntPipe 和 ParseBoolPipe 是从 @nestjs/common 包导出的。
在构建 CRUD(创建/读取/更新/删除)等功能时,基于基础实体类型创建变体通常很有用。Nest 提供了几个实用函数来执行类型转换,使这项任务更加便捷。
构建输入验证类型(也称为 DTO)时,通常需要在同一类型上创建 create(创建)和 update(更新)变体。例如,create 变体可能需要所有字段,而 update 变体则可能将所有字段设为可选。
Nest 提供了 PartialType() 实用函数来简化这一任务并减少样板代码。
PartialType() 函数返回一个类型(类),其中输入类型的所有属性都被设置为可选。例如,假设我们有一个如下所示的 create 类型:
默认情况下,所有这些字段都是必填的。要创建一个具有相同字段但每个字段都可选的类型,可使用 PartialType() 并传入类引用(CreateCatDto)作为参数:
PartialType() 函数是从 @nestjs/mapped-types 包导入的。
PickType() 函数通过从输入类型中选择一组属性来构造新类型(类)。例如,假设我们从以下类型开始:
我们可以使用 PickType() 实用函数从该类中选择一组属性:
PickType() 函数是从 @nestjs/mapped-types 包导入的。
OmitType() 函数通过从输入类型中选取所有属性,然后移除特定键集合来构造一个类型。例如,假设我们从以下类型开始:
我们可以生成一个派生类型,该类型包含除除 name 之外的所有属性,如下所示。在这个结构中,OmitType 的第二个参数是一个属性名称数组。
OmitType() 函数是从 @nestjs/mapped-types 包导入的。
IntersectionType() 函数将两种类型合并为一个新类型(类)。例如,假设我们有以下两种类型:
我们可以生成一个包含两种类型所有属性的新类型。
IntersectionType() 函数是从 @nestjs/mapped-types 包中导入的。
类型映射工具函数是可组合的。例如,以下代码将生成一个类型(类),该类型包含除 name 之外 CreateCatDto 类型的所有属性,并且这些属性将被设置为可选:
由于 TypeScript 不会存储泛型或接口的元数据,因此当您在 DTO 中使用它们时,ValidationPipe 可能无法正确验证传入的数据。例如,在以下代码中,createUserDtos 将无法被正确验证:
要验证数组,可以创建一个包含包装数组属性的专用类,或者使用 ParseArrayPipe。
此外,ParseArrayPipe 在解析查询参数时可能非常有用。让我们考虑一个 findByIds() 方法,它根据作为查询参数传递的标识符返回用户。
这种结构会验证来自 HTTP GET 请求的传入查询参数,如下所示:
虽然本章展示了使用 HTTP 风格应用程序(如 Express 或 Fastify)的示例,但 ValidationPipe 对于 WebSocket 和微服务同样适用,无论使用何种传输方法。
阅读更多关于自定义验证器、错误消息以及 class-validator 包提供的可用装饰器的信息,请点击此处 。