Search code examples
nestjsswaggernestjs-swagger

Use different schemas for request body in swagger and in controller


I have the following controller to create a new user. The request body (CreateUser) has an optional field locationName. I have a pipe which changes the locationName to null if it is not defined in the request body.

When I use @Body decorator, I can get the body properly with the locationName never being undefined in the runtime.

For Swagger, I can see that the locationName is optional. But when I try to access createUserInput.locationName in the code Typescript still complains that the value can be undefined (which I understand).

How can I introduce a different type for the controller code and another type for Swagger?

User controller:

@Post("/users")
async createUser(
  @Body(CreateUserLocationPipe) createUserInput: CreateUser
): Promise<User> {
  return await this.usersService.create(createUserInput);
}

CreateUser class:

export class CreateUser {
  @ApiProperty({ description: "The email of the user" })
  email: string;

  @ApiProperty({ description: "Location of the user" })
  locationName: string | undefined | null;
}

CreateUserLocationPipe pipe:

@Injectable()
export class CreateUserLocationPipe {
  transform(value: string | null | undefined): string | null {
    if (value.locationName === undefined) {
      value.locationName = null;
    }
  }
}

Solution

  • We can create a different class for the input that comes to the controller.

    export class CreateUserControllerInput extends OmitType(CreateUser, ["locationName"] as const) {
      locationName: WellKnownLocationName | null;
      locationPlantId: string | null;
    }
    

    Then we can differentiate the controller body from the request body of swagger like below.

    @Post("/users")
    @ApiBody({ type: CreateUser }) // This is exposed by swagger as the request body
    async createUser(
      @Body(CreateUserLocationPipe) createUserInput: CreateUserControllerInput // This is the type for the controller
    ): Promise<User> {
      return await this.usersService.create(createUserInput);
    }