Intro
Let’s assume we want to build a pretty basic NestJS To-Do application with MongoDB as a database. We’d want to have a few models that might be connected using relationships as one-to-one, one-to-many etc.
For example, we have a DTO for updating a To-Do item. It might look like this:
import { IsMongoId } from "class-validator";
class UpdateToDo {
@IsMongoId()
todoId: string;
}
With the help of using IsMongoId
decorator we are able to ensure that provided ID string is a really Mongo ObjectId
and not just a random formatted string. It’s cool, but with a one major minus - we cannot be sure that To-Do item with provided ID really exists in our database. Let’s fix that!
Implementation
Using class-validator we can build custom validators (classes or functions). Firstly, let’s define a provider that implements ValidatorConstraintInterface
import { Injectable, Type } from '@nestjs/common';
import { InjectConnection } from '@nestjs/mongoose';
import {
registerDecorator,
ValidationArguments,
ValidationOptions,
ValidatorConstraint,
ValidatorConstraintInterface,
} from 'class-validator';
import { Connection } from 'mongoose';
@ValidatorConstraint({ async: true })
@Injectable()
export class IsRelationshipProvider implements ValidatorConstraintInterface {
constructor(@InjectConnection() private readonly connection: Connection) {}
async validate(value: any, args: ValidationArguments): Promise<boolean> {
try {
const result = await this.connection.models[args.constraints[0].name].findById(value);
return !!result;
} catch (e) {
return false;
}
}
defaultMessage(args?: ValidationArguments): string {
return `${args.property} field must refer to existing ${args.constraints[0].name} document`;
}
}
Secondly, we need to define our decorator.
export const IsRelationShipWith =
<TModel extends Object>(ModelClass: Type<TModel>, options?: ValidationOptions) =>
(object: Object, propertyName: string) =>
registerDecorator({
name: `IsRelationShip`,
target: object.constructor,
propertyName,
options,
constraints: [ModelClass],
validator: IsRelationshipProvider,
});
Add the next line to bootstrap
method in main.ts
file.
import { useContainer } from 'class-validator';
useContainer(app.select(AppModule), { fallbackOnErrors: true });
The last step we have to do is to register our custom provider in Nest application. Just add IsRelationshipProvider
to providers
array of the AppModule
.
Example
Now, we can leverage the help of the decorator.
import { IsMongoId } from "class-validator";
// mongoose entity
class ToDoEntity {}
class UpdateToDo {
@IsMongoId()
@IsRelationShipWith(ToDoEntity)
todoId: string;
}
This decorator will check if provided ID really exists in the database.