Nest: A default value pipe

Created on 14 Apr 2020  路  8Comments  路  Source: nestjs/nest

Feature Request

Is your feature request related to a problem? Please describe.

When working with controllers sometimes it would be nice to provide a default value for some parameters (i.e. boolean flags).

One might assume that it is possible to do so by providing default values for function parameters, like:

@Controller('/foo')
export class FooController {
  @Get()
  async getMany(
    @Res() res: Response,
    @Query('activeOnly', ParseBoolPipe) activeOnly = false,
  ): Promise<void> {
    // ...
  }
}

Due to the implementation of primitive pipes (i.e. _ParseBoolPipe_, _ParseIntPipe_) this unfortunately would result in an error, as the decorated function's default value is applied after the pipes are done with the params and primitive pipes result in an error upon recieving an _undefined_ value.

Describe the solution you'd like

I propose an addition of a _DefaultValuePipe_ to the roster of Nest's built-in pipes.

It would be used like:

@Controller('/foo')
export class FooController {
  @Get()
  async getMany(
    @Query('activeOnly', new DefaultValuePipe(false), ParseBoolPipe) activeOnly: boolean,
    @Res() res: Response,
  ): Promise<void> {
    // ...
  }
}

Teachability, Documentation, Adoption, Migration Strategy

Nothing to add here, I think.

What is the motivation / use case for changing the behavior?

When working with Nest came upon this problem and had to write such a solution for myself, would like to share with the Nest community.

common type

Most helpful comment

@apatryda sorry mate, do you know if this new pipe have issues with this configuration?

app.useGlobalPipes(
  new ValidationPipe({
    transform: true,
    whitelist: true,
    forbidNonWhitelisted: true
  })
)

I'm getting this bad response:

{
  "statusCode": 400,
  "message": "Validation failed (numeric string is expected)",
  "error": "Bad Request"
}

when the query params are empty:

async findAll(
  @Query(
    'search',
    new DefaultValuePipe('')
  ) search: string,
  @Query(
    'offset',
    new DefaultValuePipe(0),
    ParseIntPipe
  ) offset: number,
  @Query(
    'limit',
    new DefaultValuePipe(10),
    ParseIntPipe
  ) limit: number
): Promise<Array<User>> {
  const users = await this.userService.findByRoleIds(
    [DefaultRole.Admin, DefaultRole.User],
    search,
    offset,
    limit
  )
  return users
}

Code here https://github.com/proyecto26/MyAPI/blob/master/src/controllers/user.controller.ts#L85

Thanks in advance, I'm creating a template as you can see :)

All 8 comments

Sounds reasonable, I like it! Would you like to create a PR for this issue?

Yup, sure - will do so :)

Believe my issue is related to this as well https://github.com/nestjs/nest/issues/4682

Merged & published as a part of the latest release.

@apatryda sorry mate, do you know if this new pipe have issues with this configuration?

app.useGlobalPipes(
  new ValidationPipe({
    transform: true,
    whitelist: true,
    forbidNonWhitelisted: true
  })
)

I'm getting this bad response:

{
  "statusCode": 400,
  "message": "Validation failed (numeric string is expected)",
  "error": "Bad Request"
}

when the query params are empty:

async findAll(
  @Query(
    'search',
    new DefaultValuePipe('')
  ) search: string,
  @Query(
    'offset',
    new DefaultValuePipe(0),
    ParseIntPipe
  ) offset: number,
  @Query(
    'limit',
    new DefaultValuePipe(10),
    ParseIntPipe
  ) limit: number
): Promise<Array<User>> {
  const users = await this.userService.findByRoleIds(
    [DefaultRole.Admin, DefaultRole.User],
    search,
    offset,
    limit
  )
  return users
}

Code here https://github.com/proyecto26/MyAPI/blob/master/src/controllers/user.controller.ts#L85

Thanks in advance, I'm creating a template as you can see :)

Seems like the global ValidationPipe is executed before the pipes in the @Query() decorator. I've got what I believe is the same problem.

Global ValidationPipe with transform:

app.useGlobalPipes([new ValidationPipe({ transform: true, whitelist: true })]);

A DTO:

export class CoordinatesDto {
  @IsNotEmpty()
  public latitude: string;

  @IsNotEmpty()
  public longitude: string;
}

A custom pipe for transforming the string to the DTO:

@Injectable()
export class ParseCoordinatesPipe implements PipeTransform {
  transform(value: string): CoordinatesDto {
    const [lat, lng] = value.split(',');

    const coordinates = new CoordinatesDto();
    coordinates.latitude = lat;
    coordinates.longitude = lng;

    return coordinates;
  }
}

Used the following way:

@Query('coordinates', new DefaultValuePipe('0,0'), ParseCoordinatesPipe) coordinates?: CoordinatesDto

Results in:

{
    "statusCode": 400,
    "message": [
        "latitude should not be empty",
        "longitude should not be empty"
    ],
    "error": "Bad Request"
}

Only by disabling the global ValidationPipe and adding the ValidationPipe to the @Query(), things work as expected.

@jdnichollsc Did you ever get this working using a global ValidationPipe?

@joakimbugge I think yes, but let me try your example, thanks mate!

I have another demo using that template here: https://github.com/jdnichollsc/adventure-closure

Any pull request to any of these projects are really appreciated! <3

That's the expected run order, according to how pipes are bound, global, then controller, then method, then parameter. It's documented here

Was this page helpful?
0 / 5 - 0 ratings

Related issues

tronginc picture tronginc  路  3Comments

JulianBiermann picture JulianBiermann  路  3Comments

rlesniak picture rlesniak  路  3Comments

mishelashala picture mishelashala  路  3Comments

cdiaz picture cdiaz  路  3Comments