简体   繁体   中英

How to check reference when inserting in MongoDB (typegraphql, typegoose)?

Let say I have a simple collection events created by TypeGraphql and Typegoose which stores objects like:

{ _id: ObjectId(...), name: 'SomeEvent', category: ObjectId('...') }

and corresponding type:

@ObjectType()
export class Event {
  @Field(() => ID)
  _id!: Types.ObjectId

  @prop({ ref: 'Category' })
  @Field(() => Category)
  category!: Ref<Category>

  @prop()
  @Field()
  name!: string
}

I have also collection categories which contains right now only _id and name .

Now I want to insert some Event to database. Is it possible to automatically check if categoryId provided in input exist in collection categories and if it does not, throw an error? Right now, event can be added with anything in category field and next when I try to get it by query it throws an error that category cannot be resolved because there is no category with this ID. I know, that I can check it manually during adding event but if I have more fields like that it will be problematic.

With the help of Martin Devillers answer I was able to write a validator to validate referenced documents using class-validator with typegoose.

This is my refdoc.validator.ts:

import { ValidationArguments, ValidatorConstraint, ValidatorConstraintInterface } from "class-validator";
import { Injectable } from "@nestjs/common";
import { getModelForClass } from "@typegoose/typegoose";


@ValidatorConstraint({ name: "RefDoc", async: true })
@Injectable()
export class RefDocValidator implements ValidatorConstraintInterface {

    async validate(refId: string, args: ValidationArguments) {
        const modelClass = args.constraints[0];
        return getModelForClass(modelClass).exists({ _id: refId })
    }

    defaultMessage(): string {
        return "Referenced Document not found!";
    }
}

Then I can apply it on the DTO or model with the @Validate-Decorator. The Argument I'm passing in is the typegoose model.

@Validate(RefDocValidator, [Costcenter])
costcenterId: string;

Seems to be working for me, I'm open for any improvements..

Edit: Even better with custom decorator, as Martin Devillers suggested:

refdoc.validator.ts

import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface } from "class-validator";
import { Injectable } from "@nestjs/common";
import { getModelForClass } from "@typegoose/typegoose";


@ValidatorConstraint({ name: "RefDoc", async: true })
@Injectable()
export class RefDocValidator implements ValidatorConstraintInterface {

    async validate(refId: string, args: ValidationArguments) {
        const modelClass = args.constraints[0];
        return getModelForClass(modelClass).exists({ _id: refId })
    }

    defaultMessage(): string {
        return "Referenced Document not found!";
    }
}

export function RefDocExists(modelClass: any, validationOptions?: ValidationOptions) {
    return function (object: Object, propertyName: string) {
        registerDecorator({
            name: 'RefDocExists',
            target: object.constructor,
            propertyName: propertyName,
            constraints: [modelClass],
            options: validationOptions,
            validator: RefDocValidator,
        });
    };
}

Then you can use it on the DTO like:

@ApiProperty()
@IsNotEmpty()
//@Validate(RefDocValidator, [Costcenter]) old
@RefDocExists(Costcenter) //new
costcenterId: string;

Out of the box, neither MongoDB nor mongoose nor typegoose offer any automated referential integrity checks.

At the database level, this feature doesn't exist (which is also one of the fundamental differences between a database like MongoDB and SQL Server/Oracle).

However, at the application level, you can achieve this behavior in various ways:

  1. Middleware . Mongoose middleware allows you add generalized behavior to your models. This is useful if you're inserting documents in your EventModel in various places in your codebase and don't want to repeat your validation logic. For example:
EventModel.path('category').validate(async (value, respond) => {
    const categoryExists = await CategoryModel.exists({ _id: value })
    respond(categoryExists)
})
  1. Mongoose plugins . A plugin like mongoose-id-validator allows you to add the above behavior to any type of document reference in all your schemas.
  2. Manually . Probably the least favorite option, but I'm mentioning it for completeness sake: with mongoose's Model.exists() you can achieve this with a one-liner like: const categoryExists = await CategoryModel.exists({ _id: event.category })

To reiterate: all the above options are stopgaps. It's always possible for someone to go directly in your database and break referential integrity anyway.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM