Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NestJS: How to access both Body and Param in custom validator?

I've a scenario where I need values from both values in the param and body to perform custom validation. For example, I've a route /:photoId/tag that adds a tag to a photo.

However, before it can add a tag to a photo, it has to validate whether there is already an existing tag of the same name with the photo.

I have the following route in my controller:

@Post(':photoId/tag')
@UsePipes(new ValidationPipe())
async addTag(
    @Param() params: AddTagParams,
    @Body() addTagDto: AddTagDto
) {
    // ...
}

Since the :photoId is provided as a param and the tag is provided in the body of the request, they can't access each other in the custom validator and I can't use both pieces of information to do a check against the database:

export class IsPhotoTagExistValidator implements ValidatorConstraintInterface {

    async validate(val: any, args: ValidationArguments) {
        // supposed to check whether a tag of the same name already exists on photo
        // val only has the value of photoId but not the name of the tag from AddTagDto in Body
    }
}   


export class AddTagParams{
   @IsInt()
   @Validate(IsPhotoTagExistValidator)   // this doesn't work because IsPhotoTagExistValidator can't access tag in AddTagDto
   photoId: number
}

export class AddTagDto{
   @IsString()
   tag: string
}

As in the example above, the val in IsPhotoTagExistValidator is only the photoId. But I need both the photoId in Param and tag name in the Body to check whether the particular photoId already has that tag.

How should I access both the Body and Param in the custom validator function? If not, how should I approach this problem?

like image 401
Carven Avatar asked Apr 02 '19 18:04

Carven


People also ask

What is DTO NestJS?

A DTO is an object that defines how the data will be sent over the network. We could determine the DTO schema by using TypeScript interfaces, or by simple classes. Interestingly, we recommend using classes here.

What is ValidationPipe in NestJS?

The in-built NestJS ValidationPipe is a great way to handle validations in an application. Validations are an extremely important aspect of any production-level application. Often, developers have to write large amounts of code to handle even basic validations. This is largely counter-productive.

How do you implement a validator?

Implementing the Validator Interface A Validator implementation must contain a constructor, a set of accessor methods for any attributes on the tag, and a validate method, which overrides the validate method of the Validator interface.


1 Answers

The only solution I have found so far was derived from this comment https://github.com/nestjs/nest/issues/528#issuecomment-497020970

context.interceptor.ts

import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'
import { Observable } from 'rxjs'

/**
 * Injects request data into the context, so that the ValidationPipe can use it.
 */
@Injectable()
export class ContextInterceptor implements NestInterceptor {
  intercept(
    context: ExecutionContext,
    next: CallHandler
  ): Observable<any> {
    const request = context.switchToHttp().getRequest();

    request.body.context = {
      params: request.params,
      query: request.query,
      user: request.user,
    };

    return next.handle()
  }
}

main.ts

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalInterceptors(new ContextInterceptor());
  // ...
}

If you use {whitelist: true} in ValidationPipe params you will need to allow context in your Dto objects.

this can be done by extending such Dto:

context-aware.dto.ts

import { Allow } from 'class-validator';

export class ContextAwareDto {
  @Allow()
  context?: {
    params: any,
    query: any,
    user: any,
  }
}

After this, you will be able to access request data when validating body in custom validator via validationArguments.object.context

You can easily adjust the above to access the context when validating params or query, although I find it sufficient to have this only during body validation.

like image 122
Yura Fedoriv Avatar answered Oct 18 '22 10:10

Yura Fedoriv