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?
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With