mxxnseat
mxxnseat

Reputation: 358

How validate query params in nestjs

Yo, i have store application with nestjs, i need validate mongo id, which is pass by query, the problem is that i also pass and search query. I write pipe which validate all values, and exclude this search query

@Injectable()
export class ValidationObjectId implements PipeTransform {
    transform(value: UniqueId, metadata: ArgumentMetadata) {
        if (
            !Types.ObjectId.isValid(value) &&
            metadata.data !== "searchString"
        ) {
            throw new BadRequestException("Неверный параметр запроса");
        }

        return value;
    }
}

But this code not reusable for other case. I want get some examples, how i can do this

Upvotes: 21

Views: 67862

Answers (2)

zemil
zemil

Reputation: 5066

For single query param usage, you can play with this example:

import { ArgumentMetadata, BadRequestException, Injectable, PipeTransform } from '@nestjs/common';

type TOptional<T> = T | undefined;

@Injectable()
export class ValidateEnum<T extends Record<string, unknown>> implements PipeTransform<T> {
    constructor(public readonly enumObj: T, public readonly isRequired = false) {}

    async transform(value: TOptional<T>, { data: argName }: ArgumentMetadata): Promise<TOptional<T>> {
        const withValidation = this.isRequired ? true : value !== undefined;
        if (withValidation) {
            const enumValues = Object.values(this.enumObj);
            if (!enumValues.includes(value)) {
                throw new BadRequestException(`Invalid ${argName}=${value} - possible values: ${enumValues.join('|')}`);
            }
        }

        return value;
    }
}

If you want to use a pipe with a DTO object, check this out:

import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';
import { plainToInstance, Type } from 'class-transformer';
import { IsInt, IsOptional } from 'class-validator';

export class PaginationRequestDto {
    @Type(() => Number)
    @IsInt()
    @IsOptional()
    public readonly page?: number;

    @Type(() => Number)
    @IsInt()
    @IsOptional()
    public readonly take?: number;
}

@Injectable()
export class PaginationTransformPipe implements PipeTransform {
    async transform(dto: PaginationRequestDto, { metatype }: ArgumentMetadata) {
        if (!metatype) {
            return dto;
        }

        return plainToInstance(metatype, dto);
    }
}

And usage example:

@Get('')
// @UsePipes(new PaginationTransformPipe()) // also possible
public async someOperation(
    @Query(new PaginationTransformPipe()) pagination: PaginationRequestDto,
    @Query('status', new ValidateEnum(ESomeEnum)) status?: ESomeEnum
    ) {
    // ...your logic
}

For someone, it can be helpful to use default pipes

Upvotes: 2

Waldemar Lehner
Waldemar Lehner

Reputation: 1065

The cleanest and most reusable approach would probably be to make use of the ValidationPipe with a Query-DTO-Class.

Take a look at the following example.

https://gitlab.com/WaldemarLehner/nestjs-swagger-example/-/tree/1aea48597ddcf93b0a0d1449fe5087413415bbee

Inside the Controller you can pass a Pipe to the @Query()-Decorator. You can use the ValidationPipe which already comes with Nest and makes use of the class-validator and class-transformer Packages.

You can create a DTO-Class for your Query-Parameters as done in the PostHelloQuery.dto.ts from my example.

import { IsBoolean, IsOptional } from "class-validator";

export class PostHelloQueryDTO {
    @IsOptional()
    @IsBoolean()
    public useExclamation?: boolean;
}

Here you define constraints for your Data using decorators from class-validator. For a list of all decorators, please refer to https://github.com/typestack/class-validator#validation-decorators .

If none of the validators fit your needs you can also create your own Decorator as shown here.

In my example, the useExclamation-Query Param is an optional boolean. Note that incoming query parameters are parsed as strings.

The conversion is done using the enableInplicitConversion-Option as seen in the Controller:

@Query(new ValidationPipe({
    transform: true,
    transformOptions: {enableImplicitConversion: true},
    forbidNonWhitelisted: true
})) query: PostHelloQueryDTO

For more information about using ValidationPipe with class-validator, you can take a look at the NestJS Documentation:

https://docs.nestjs.com/techniques/validation

For your specific Use-Case (Validating MongoDB IDs), I have found an open Issue with an Example Implementation for a @IsMongoDB-Decorator:

https://github.com/typestack/class-validator/issues/630#issuecomment-645638436

Upvotes: 50

Related Questions