清理你的 NestJS 控制器:在 DTO 中进行远程 API 验证 🧼🚀
Source: Dev.to

“远程验证” 的困扰
如果你使用 NestJS,可能已经爱上了 class-validator。在属性上加上 @IsEmail() 装饰器,然后安心睡觉,因为错误数据不会进入你的服务,这种感觉非常棒。
但当我们需要 远程验证 时,这种安心感往往会消失。
你一定遇到过这样的场景:
- 前端发送
userId、couponCode或transactionId。 - 你需要调用外部微服务或 API 来检查它是否有效。
- 如果有效,通常还需要获取额外的数据(比如用户的姓名)并保存到本地。
“脏” 方法(我们都做过)
// ❌ The common anti-pattern
@Post()
async create(@Body() dto: CreateOrderDto) {
// Validation logic leaking into the Controller/Service
const response = await this.http.get(`https://external-api.com/users/${dto.userId}`);
if (response.status !== 200) {
throw new BadRequestException('Invalid User ID');
}
// Business logic finally starts here...
}
这违背了 NestJS 的约定。控制器应该只处理请求,服务层负责业务逻辑,验证应当停留在入口——DTO 中。
解决方案:nestjs-remote-validate
我厌倦了写重复的样板代码,于是构建了一个库,能够在 DTO 装饰器内部进行 HTTP 验证。
// ✅ The clean way
export class CreateOrderDto {
@ValidateApi({
host: 'https://external-api.com/users/:userId', // Dynamic injection!
method: 'GET',
validate: ({ status }) => status === 200,
})
userId: string;
}
杀手级特性:DTO 丰富化 🐱
很多时候你不仅想验证,还想 使用 API 返回的数据。该库允许你从响应中提取数据并自动注入到 DTO 的另一个字段。
export class CreateTransactionDto {
// 1. Validates if the ID exists remotely
@ValidateApi({
host: 'https://bank-api.com/accounts/:accountId',
method: 'GET',
validate: ({ status }) => status === 200,
// 2. Extracts the owner name from the response body
extractValue: (body) => body.ownerName,
// 3. Injects it into the field below
targetField: 'accountOwnerName',
})
accountId: string;
// This field gets populated "magically" after validation!
@Allow()
accountOwnerName: string;
}
当 DTO 抵达控制器时,accountOwnerName 已经被填充。服务代码中不再需要任何 HTTP 调用。 🤯
快速开始
1. 安装
npm install nestjs-remote-validate
2. 配置(重要!)
验证器需要执行异步操作并进行注入,所以要注册 provider 并告诉 class-validator 使用 NestJS 容器。
app.module.ts
import { ApiValidatorConstraint } from 'nestjs-remote-validate';
@Module({
providers: [ApiValidatorConstraint], // Register the provider
})
export class AppModule {}
main.ts
import { useContainer } from 'class-validator';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Allow dependency injection inside validators
useContainer(app.select(AppModule), { fallbackOnErrors: true });
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
await app.listen(3000);
}
bootstrap();
当前限制与路线图(一起构建!)
- HTTP 客户端:目前使用原生
fetch。后续计划引入 Axios 注入,以复用全局拦截器(日志、认证等)。 - 缓存:每次验证都会发起请求。实现 缓存策略(内存/Redis)可以提升重复验证的性能。
- 批量请求:一次调用即可验证 ID 数组。
这些都是 “Good First Issues” 或者稳健 PR 的绝佳机会。
贡献! 🤝
如果这个库解决了你的痛点,欢迎尝试!对 Axios 支持或 缓存 的想法非常欢迎——直接在仓库里提出。每一个 star、issue 或 PR 都会让生态系统更健康。
- GitHub:
- NPM:
祝编码愉快! 🚀