expressvalidationnestjsclass-validator

Validation an object with at least one non-nullable field


Problem:
Backend will receive an object, and at least one field must to be non-nullable.

Example dto object:

export class MessageDto implements TMessage {
    @IsString()
    @MinLength(1)
    roomId: TValueOf<Pick<Room, "id">>;

    @IsArray()
    @MinLength(1, {
        each: true,
    })
    attachmentIds: TValueOf<Pick<File, "id">>[];

    @IsString()
    text: TValueOf<Pick<Message, "text">>;

    @IsOptional()
    @IsString()
    replyToMessageId: TValueOf<Pick<Message, "id">> | null;
}

Explanation - the above-mentioned object may contain neither attachmentIds nor text. I want to be sure that either attachmentIds.length > 0 or text.length > 0.

Question:
Is there a way to validate not only one field, but also the entire object(or fields in couples) using "class-validator" package that is contained by default in NestJs framework? I can't believe, that the "class-validator" package is the bottleneck of the project and I will have to replace it with another one, like "yup" or "zod".

Workaroung:
I just revalidate that object in NestJs.Controller. But I want to make the code clearer.


Solution

  • I will propose the same solution I implemented in my project. The logic is to:

    It has to be pointed out, that the 'inputType' cannot be empty, it must be mandatory. Therefore, your goal will be achieved, as to at least one of the possible inputs in NOT empty.

    enum InputType {
        ATTACHMENT = 'attachment',
        TEXT = 'text',
    }
    
    export class MessageDto implements TMessage {
        @IsString()
        @MinLength(1)
        roomId: TValueOf<Pick<Room, "id">>;
    
        @IsEnum(InputType)
        inputType!: InputType;
    
        @ValidateIf((o) => o.inputType === InputType.ATTACHMENT)
        @IsArray()
        @MinLength(1, {
            each: true,
        })
        @IsNotEmpty()
        attachmentIds: TValueOf<Pick<File, "id">>[];
    
        @ValidateIf((o) => o.inputType === InputType.TEXT)
        @IsString()
        @MinLength(1)
        text: TValueOf<Pick<Message, "text">>;
    
        @IsOptional()
        @IsString()
        replyToMessageId: TValueOf<Pick<Message, "id">> | null;
    }