Search code examples
typescriptmongoosenestjstypegraphqltypegoose

Validation failed: Cast to Array failed for value


I am trying to use Typegoose with GrqphQL, MongoDB and Nest.js. I wanted to create a mutation which will create a post. I created model, service and resolver for simple Post. When I am trying to run my mutation to create a Post I am getting this error:

PostModel validation failed: sections: Cast to Array failed for value:

[
  [Object: null prototype] {
    title: 'section 1',
    body: [ 'section test lalal' ],
    code: [ "console.log('hello world!)" ],
    image: [ 'kanow.svg' ]
  }
]

at path "sections"

I do not know why I am getting this error I tried to use: ref, itemsRefs (for class and string value. You can read more about this here: typegoose arrayProp). After I create new PostModel and I console.log every property I can see that sections is an empty array, but it shouldn't, because inside postInput I can find this JSON:

[Object: null prototype] {
  title: 'refactored post',
  image: 'rest.jpg',
  tags: [ 'ref, ref' ],
  sections: [
    [Object: null prototype] {
      title: 'section 1',
      body: [Array],
      code: [Array],
      image: [Array]
    }
  ]
}

I think this json looks good so it shouldn't be a source of this error.

I want to know what I am doing wrong and why it is not working. Below I attached some code. If you need anything else let me know in comments.

GraphQL mutation:

mutation {
  createPost(postInput: {
    image: "rest.jpg",
    title: "refactored post"
    tags: ["ref, ref"]
    sections: [{
      title: "section 1"
      body: ["section test lalal"]
      code: ["console.log('hello world!)"]
      image: ["kanow.svg"]
    }]
  }) {
    _id
    title
    tags
    sections {
      title
      body
      code
      image
    }
  }
}

post.service.ts:

@Injectable()
export class PostsService {
    constructor(@InjectModel(PostModel) private readonly postModel: ReturnModelType<typeof PostModel>) {
    }

    async create(postInput: CreatePostInput): Promise<DocumentType<PostModel>> {
        const createdPost: DocumentType<PostModel> = new this.postModel(postInput);
        return await createdPost.save();
    }
 ...
}

post.model.ts:

@ObjectType()
export class PostModel {
    @Field(() => ID)
    readonly _id: ObjectId;

    @Field()
    @prop({required: true})
    title: string;

    @Field()
    @prop({nullable: true})
    image: string;

    @Field(() => [String])
    @arrayProp({items: String})
    tags: string[];

    @Field(() => [SectionModel])
    @arrayProp({ref: 'SectionModel'})
    sections: Ref<SectionModel>[];
}

section.model.ts:

@ObjectType()
export class SectionModel {
  @Field()
  @prop({ required: true })
  title: string;

  @Field(() => [String])
  @arrayProp({ items: String })
  body: string[];

  @Field(() => [String])
  @arrayProp({ items: String })
  code: string[];

  @Field(() => [String])
  @arrayProp({ items: String })
  image: string[];
}

create-post.input.ts:

@InputType()
export class CreatePostInput {
  @Field()
  readonly title!: string;

  @Field()
  readonly image!: string;

  @Field(() => [String])
  readonly tags!: string[];

  @Field(() => [CreateSectionInput])
  readonly sections!: CreateSectionInput[];
}

UPDATE 1

I discovered that if I pass an empty array inside sections body then there is no problem to create a post. I attach example query below:

mutation {
  createPost(postInput: {
    image: "newly created.jpg",
    title: "newly created"
    tags: ["newly created, ref"]
    sections: []
  }) {
    _id
    image
    title
    tags
    sections {
      title
      body
      code
      image
    }
  }
}

Solution

  • I figured out that I should not use Ref<> for declaring nested documents. To do it you have to use items and provide correct typgoose model class. In the documentation you can read that items are only for primitive types but in the end of items documentation description you can read that you can use it also for typegoose classes (for example SectionModel.ts). Below I attached an example which is solving my problem.

    post.model.ts:

    @ObjectType()
    export class PostModel {
      @Field(() => ID)
      readonly _id: ObjectId;
    
      @Field()
      @prop({required: true})
      title: string;
    
      @Field()
      @prop({nullable: true})
      image: string;
    
      @Field(() => [String])
      @arrayProp({items: String})
      tags: string[];
    
      @Field(() => [SectionModel])
      @arrayProp({items: SectionModel})
      sections: SectionModel[];
    }
    

    section.model.ts:

    @ObjectType()
    export class SectionModel {
      @Field(() => ID)
      readonly _id: ObjectId;
    
      @Field()
      @prop({ required: true })
      title: string;
    
      @Field(() => [String])
      @arrayProp({ items: String })
      body: string[];
    
      @Field(() => [String])
      @arrayProp({ items: String })
      code: string[];
    
      @Field(() => [String])
      @arrayProp({ items: String })
      image: string[];
    }