如何在 Nestjs 独立应用程序中使用增强器(管道、守卫、拦截器等)

How to use enhancers (pipes, guards, interceptors, etc) with Nestjs Standalone app

Nestjs 模块系统很棒,但我正在努力弄清楚如何在无服务器设置中充分利用它。

我喜欢在 *.service.ts 文件中编写我的域逻辑的方法,同时使用 *.controller.ts 文件来处理非业务相关的任务,例如验证 HTTP 请求正文并将其转换为在服务中调用方法之前的 DTO。

我在 nestjs 文档中找到了关于 Serverless 的部分,并确定对于我的特定用例,我需要使用“独立应用程序功能”。

我创建了一个sample nestjs app here来说明我的问题。

示例应用程序有一个简单的 add() 函数来添加两个数字。我使用 class-validatorAddDto class.

上进行验证
// add.dto.ts
import { IsNumber } from 'class-validator'

export class AddDto {
    @IsNumber()
    public a: number;
        
    @IsNumber()
    public b: number;
}

然后,通过一些 Nestjs 魔法,我可以通过执行以下操作在我的控制器中使用 AddDto 获得内置验证:

// main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Use `ValidationPipe()` for auto-validation in controllers
  app.useGlobalPipes(
    new ValidationPipe({ transform: true })
  )

  await app.listen(3000);
}


// app.controller.ts
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Post('add')
  add(@Body() dto: AddDto): number {
    // Request body gets auto validated and converted
    // to an instance of `AddDto`, sweet!
    return this.appService.add(dto.a, dto.b);
  }
}

// app.service.ts
@Injectable()
export class AppService {
  add(a: number, b: number): number {
      return a + b
  }
}

到目前为止,还不错。现在在 AWS 中使用 Lambda 函数时会出现问题,即:

这个问题docs hint:

Be aware that NestFactory.createApplicationContext does not wrap controller methods with enhancers (guard, interceptors, etc.). For this, you must use the NestFactory.create method.

例如,我有一个从 AWS EventBridge 接收消息的 lambda。以下是示例应用程序的片段:

// standalone-app.ts
interface IAddCommand {
  a: number;
  b: number;
}

export const handler = async (
  event: EventBridgeEvent<'AddCommand', IAddCommand>,
  context: any
) => {
  const appContext = await NestFactory.createApplicationContext(AppModule);
  const appService = appContext.get(AppService);
  const { a, b } = event.detail;
  const sum = appService.add(a, b)
  // do work on `sum`, like cache the result, etc...
  return sum
};

// lambda-handler.js
const { handler } = require('./dist/standalone-app')

handler({
  detail: {
   a: "1", // is a string, should be a number
   b: "2" // is a string, should be a number
  }
})
  .then(console.log) // <--- prints out "12" ("1" + "2") instead of "3" (1 + 2)

我在 event.detail 中没有得到事件有效负载的“免费”验证,就像我在向 /add 发出 HTTP POST 请求时对 @Body() dto: AddDto 所做的那样。优选地,代码会在上面的示例中抛出验证错误。相反,我得到了 "12" 的答案——误报。


希望这能说明我问题的症结所在。我仍然想在调用 appService.add(a, b) 之前验证事件的有效负载,但我不想在 app.controller.ts.

中编写已存在于控制器上的自定义验证逻辑

想法?其他人 运行 以前参与过这个吗?

我在写这个庞大的问题时想到我可以在我的 Lambda 处理程序中简单地使用 class-validatorclass-transformer

import { validateOrReject } from 'class-validator'
import { plainToClass } from 'class-transformer'
import { AddDto } from 'src/dto/add.dto'

export const handler = async (event: any, context: any) => {
  const appContext = await NestFactory.createApplicationContext(AppModule);
  const appService = appContext.get(AppService);

  const data = getPayloadFromEvent(event)

  // Convert raw data to a DTO
  const dto: AddDto = plainToClass(AddDto, data)

  // Validate it!
  await validateOrReject(dto)

  const sum = appService.add(dto.a, dto.b)
  // do work on `sum`...
}

它不像使用 app.useGlobalPipes(new ValidationPipe()) 那样“免费”,但只涉及几行额外的代码。