Mongoose sub-documents inside nested object - node.js

I have a schema which uses multiple fields as reference to other collections in the database.
Everything seems to work fine except for a sub-document which is present in a nested object.
When I try to add the document as a reference to that particular key (role) in a nested object (metadata), instead of the ObjectId the entire object gets saved.
This is my schema:
class Metadata {
// THIS DOES NOT WORK FINE AND IT STORES THE COMPLETE OBJECT
// AND ALSO EMPTY ARRAY IS NOT CREATED UPON THE DOCUMENT CREATION
// WHICH IS DEFAULT BEHAVIOUR OF MONGOOSE
#Prop({
ref: 'Role',
type: [mongoose.Schema.Types.ObjectId]
})
roles: Role[];
}
#Schema({...})
export class User {
#Prop()
name: string;
#Prop()
password: string;
// This works fine and it only stores the ObjectId
#Prop({
ref: 'Favourite',
type: [mongoose.Schema.Types.ObjectId]
})
favourties: Favourite[]
#Prop({type: Metadata})
metadata: Metadata;
// WHEN THE SAME IS REMOVED OUT OF METADATA OBJECT, IT WORKS
// FINE AND STORES ONLY OBJECT ID
#Prop({
ref: 'Role',
type: [mongoose.Schema.Types.ObjectId]
})
roles: Role[];
}
I'm using "#nestjs/mongoose": "^9.2.1" and "mongoose": "^6.8.2".

If you want to embed a reference to the document in metadata collection, you should refer it by the objectId in the User document itself.
#Schema({collection: 'metadata'})
class Metadata {
#Prop({
ref: 'Role',
type: [mongoose.Schema.Types.ObjectId]
})
roles: Role[];
}
#Schema({...})
export class User {
#Prop()
name: string;
#Prop()
password: string;
#Prop({
ref: 'Favourite',
type: [mongoose.Schema.Types.ObjectId]
})
favourties: Favourite[]
#Prop({
ref: 'Metadata',
type: [mongoose.Schema.Types.ObjectId]
})
metadata: Metadata;
}

Related

NestJS GeoJSON in mongoDB error on type field

I am using NestJS and #nestjs/mongoose to create a schema that needs to store a GeoJSON coordinate. Below is the schema for the location schema which has a field labeled point, this stores the point schema
import { Prop, Schema } from '#nestjs/mongoose';
import { ApiProperty } from '#nestjs/swagger';
#Schema()
export class PointSchema {
#ApiProperty()
#Prop({
type: String,
enum: ['Point'],
default: 'Point',
required: true
})
type: string
#ApiProperty({
type: [Number, Number]
})
#Prop({
type: [Number, Number],
required: true,
})
coordinates: number[]
}
#Schema()
export class Location {
#ApiProperty({
type: PointSchema
})
#Prop({
type: PointSchema
})
point: PointSchema
#ApiProperty()
#Prop()
addressShort: string;
#ApiProperty()
#Prop()
addressLong: string;
#ApiProperty()
#Prop()
geohash: string;
}
The issue is that when I go to save a new location, I get an error stating that the point field cannot be converted into an object field, since it is a string.
location.point.type: Cast to Object failed for value "Point" (type string) at path "point.type", location.point.type.coordinates: Path point.type.coordinates is required.
I am not sure why this happens, but if instead I change the object shape right before saving it to match what the error wants
const newLocation:Location = {
...location,
point: {
type: newPlace.location.point
}
}
Then it works and will save the GeoJSON coordinates in the type field of the point. I'm not sure why it is trying to save the entire point object inside the type field, and I am worried that this will not be indexable. Has anyone experienced this before or has worked with NestJS mongo db and tried to use GeoJSON objects? Thank you.

How to reference an another class with non _id field in typegoose?

I'm using Typegoose v9.3.1
I need to create a reference for another collection with non _id field using Typegoose.
For example i'm having the following models.
Class collection
export class Class {
#prop({ required: true })
public _id!: string;
#prop({ required: true })
public grade!: Source;
#prop({ autopopulate: true, ref: Student, type: String })
public students!: Ref<Student, string>[];
}
Student Collection
export class Student {
#prop({ required: true })
public rollNo!: string;
#prop({ required: true })
public name!: string;
}
From the above example Class collection referencing the student collection based on the _id field (by default) of Student collection. But i need to create a reference based on the rollNo field(non _id field).
Thanks in Advance!
The only thing i know of for this case are virtual populates:
Mongoose: Virtual Populate
Typegoose: Virtual Populate
I am not aware of any method to change the field the prop-option ref uses, so i can only provide a virtual populate example.
Note: this example may not work in all cases, like if you have specific data layout requirements.
Example:
export class Class {
#prop({ required: true })
public _id!: string;
#prop({ required: true })
public grade!: Source;
#prop({ autopopulate: true, ref: () => Student, foreignField: "classId localField: "_id" })
public students!: Ref<Student, string>[];
}
export class Student {
#prop({ required: true, ref: () => Class }) // this could also be done without "ref" and just setting the type to "ObjectId"
public classId: Ref<Class>;
#prop({ required: true })
public rollNo!: string;
#prop({ required: true })
public name!: string;
}
If the above example does not work in your case, then i think your only option is to use a manual query / aggregation.

Nestjs: Correct schema for array of subdocuments in mongoose (without default _id or redefine ObjectId)

I am working with Nest.js and trying to create a Schema with decorators which contain array of sub-document field.
I don't have any troubles, with importing/export the Schema and converting it to a model, until:
I receive the following error in my service file.
After hours of googling, I discover that the real reason is behind the array sub-document fields, and only with them. As soon, as I remove the members field. the schema & model will be fine. And have to do nothing with the solution described in following, or any other answers relevant with extends Mongoose.Document. (If you have already done it)
Most of cases, that I found, are relevant with sub-documents, but not array sub-document. And I'd like to ask:
How to correctly create a field with an array of subdocuments in Nestjs via mongoose / Typescript with using of decorators?
And unseed this error:
S2344: Type 'Guild' does not satisfy the constraint 'Document<any, {}>'.   
The types returned by 'delete(...).$where(...).cast(...)' are incompatible between these types.
Type 'UpdateQuery<Guild>' is not assignable to type 'UpdateQuery<Document<any, {}>>'.
Type 'UpdateQuery<Guild>' is not assignable to type '_UpdateQuery<_AllowStringsForIds<LeanDocument<Document<any, {}>>>>'.
Types of property '$pull' are incompatible.
Type 'PullOperator<_AllowStringsForIds<LeanDocument<Guild>>>' has no properties in common with type 'PullOperator<_AllowStringsForIds<LeanDocument<Document<any, {}>>>>'.
My Schema is:
import { Document, Schema as MongooseSchema } from 'mongoose';
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
class GuildMember {
#Prop({ type: String, required: true, lowercase: true })
_id: string;
#Prop({ required: true })
id: number;
#Prop({ required: true })
rank: number;
}
#Schema({ timestamps: true })
export class Guild extends Document {
#Prop({ type: String, required: true, lowercase: true })
_id: string;
#Prop({ type: MongooseSchema.Types.Array})
members: GuildMember[]
}
export const GuildsSchema = SchemaFactory.createForClass(Guild);
What have I done.
Various ways including:
Cover sub-document Class with #Schema() decorator and adding extends Document
Adding type: to field #Prop() decorator:
#Prop({ type: [GuildMember] })
members: GuildMember[]
or vice-versa. It's ok for primitives, but not for Class embedded documents.
adding #Prop({ ref: () => GuildMember })
And following the official NestJs docs:
#Prop({ type: [{ type: MongooseSchema.Types.Array, ref: GuildMember }] })
members: GuildMember[]
It still doesn't help. I thought, that it could be relevant, not just with mongoose.Document, but also another type, which is: mongoose.DocumentArray
Updated:
According to current progress. It seems that problem is relevant with the field: type value of default mongoose, not the #Prop decorator itself. So even if I write something like that:
#Prop()
members: Types.Array<GuildMember>
it still gives an error. Type is imported from: import { Document, Schema as MongooseSchema, Types } from 'mongoose';
UPDATED:
Working solution:
with redefined _id for other TS primitive:
import { Document, Schema as MongooseSchema, Types } from "mongoose";
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
#Schema()
class Pet extends Document {
#Prop({ type: Number })
_id: number;
#Prop({ type: String })
name: string;
}
export const PetsSchema = SchemaFactory.createForClass(Pet);
Parent schema array of objects field:
#Prop({ type: [PetsSchema] })
pets: Types.Array<Pet>;
If you want to disable build-in ObjectId at all
Use the following #Prop decorator and remove _id from child schema:
#Prop({ _id: false, type: [PetsSchema] })
pets: Types.Array<Pet>;
Your members prop is not a simple array. It is a collection of sub docs and should be declared as [SchemaTypes.ObjectId] which will implement sub documents with _id field via default mongo ObjectID value:
#Prop({ type: [SchemaTypes.ObjectId], ref: 'GuildMember'})
members: GuildMember[]

When using MongoDB with TypeScript, should I add an _id field to the interfaces of my models?

This is the fist time I use MongoDB with Mongoose and Typescript. I am defining an interface for each model of my database.
Should I add the _id field to the interfaces I create, like I am doing in this code snippet?
import mongoose, { Model, Document, ObjectId } from "mongoose";
export interface IUser extends Document {
_id: ObjectId;
name: string;
email: string;
password: string;
created: Date;
isPremium: boolean;
img?: string;
applicantProfile?: string;
businessProfile?: string;
}
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true },
password: { type: String, required: true },
isPremium: { type: Boolean, required: true, default: true },
created: { type: Date, required: true, default: Date.now },
img: String,
applicantProfile: String,
businessProfile: String,
});
export const User: Model<IUser> = mongoose.model("User", userSchema)
The tutorials I have read about Mongo + Typescript don't do this, but I have found that, if I don't declare the _id field in my interface, then, when I try to access the _id field, it has a value of any, which I don't like.
I'd like to know how you create the interfaces of your models and whether adding the _id field to the interface is a good practice.

Typegoose ref with array of subdocuments

I'm trying to get nested subdocuments array using Typegoose.
Before refactoring with Typegoose, I had this working code with mongoose :
Interface :
export interface IFamily extends Document {
name: string;
products: IProduct[];
}
Schema :
const familySchema: Schema = new Schema({
name: { type: String },
products: [{ type: Schema.Types.ObjectId, ref: 'Product' }]
});
When I did Family.findById('5f69aa0a56ca5426b44a86c5'), I had an array of Product ObjectId in my JSON result.
After refoctoring, I use Typegoose :
Class :
#modelOptions({ schemaOptions: { collection: 'families' } })
export class Family {
#prop({ type: Schema.Types.ObjectId })
public _id?: string;
#prop({ type: String, required: false })
public name?: string;
#prop({ ref: () => Product, required: true, default: [] })
public products!: Product[];
}
When I do :
getModelForClass(Family).findById('5f69aa0a56ca5426b44a86c5')
property "products" with an array of ObjectId is not in the result (property is absent) :
{
"_id": "5f69aa0a56ca5426b44a86c5",
"name": "Fourniture"
}
I can't figue how to make that working. I think the problem is in the family class #prop(ref). I saw some example where peoples used #arrayProp but now is deprecated.
I find documentation about ref with a simple object but not with an array of object with version 5.9.1 of Typegoose.
Thanks
aside from using an old version of typegoose with new "syntax"
this is how it is supposed to look in typegoose (7.4)
#modelOptions({ schemaOptions: { collection: 'families' } })
export class Family {
#prop()
public _id?: string;
#prop()
public name?: string;
#prop({ ref: () => Product, required: true, default: [] })
public products!: Ref<Product>[]; // if Product's "_id" is also "string", then it would need to be ": Ref<Product, string>[];"
}

Resources