Nestjs Mongoose nested schema is not creating default values - mongodb

Here is my code that I used in my entity
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import * as mongoose from 'mongoose';
#Schema({ _id: false })
export class Current {
#Prop({ default: '' })
operation: string;
#Prop({
default: '',
enum: [rentReportStatus.optin, rentReportStatus.optout, ''],
})
status: string;
#Prop({ default: Date.now() })
createdAt: Date;
}
const CurrentSchema = SchemaFactory.createForClass(Current);
#Schema({ collection: 'tests', timestamps: true })
export class Test extends BaseEntity {
#Prop({ type: CurrentSchema })
current: Current;
#Prop()
userId: mongoose.Schema.Types.ObjectId;
#Prop()
createdBy: mongoose.Schema.Types.ObjectId;
#Prop()
updatedBy: mongoose.Schema.Types.ObjectId;
}
export const RentReportingSchema = SchemaFactory.createForClass(RentReporting);
I tried many ways but current is not getting initialized with default values during save operation

Related

How to add timestamps fields and _id into nested schema by default

I have a correct schema User where fields _id, createdAt, updatedAt are written by default. But in schema Message it doesn't work.
export type UserDocument = HydratedDocument<User>;
#Schema({ timestamps: true, versionKey: false })
export class Message {
#Prop()
content: string;
}
#Schema({ timestamps: true, versionKey: false })
export class User implements UserInterface {
#Prop()
name: string;
#Prop()
email: string;
#Prop([{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }])
messages?: Message[];
}
export const UserSchema = SchemaFactory.createForClass(User);
Here is my function of message creating:
async createMessage() {
const user = await this.userModel.findById('63ee4fc044d93a4f6bebf934');
user.messages.push({ content: 'a' });
return await user.save();
}
And error is:
Cast to ObjectId failed for value "{ content: 'a' }" (type Object) at path "messages" because of "BSONTypeError"
But this snippet works fine:
async createUser(createUserDto: CreateUserDto): Promise<CreatedUserDto> {
return this.userModel.findOneAndUpdate(
{ name: createUserDto.name },
createUserDto,
{ upsert: true, new: true },
);
}
How to fix it?
Fixed id, correct implementation:
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import { HydratedDocument } from 'mongoose';
import { UserInterface } from '../interface/user.interface';
export type UserDocument = HydratedDocument<User>;
#Schema({ timestamps: true, versionKey: false })
export class Message {
#Prop()
content: string;
}
export const MessageSchema = SchemaFactory.createForClass(Message);
#Schema({ timestamps: true, versionKey: false })
export class User implements UserInterface {
#Prop()
name: string;
#Prop()
email: string;
#Prop({ type: [MessageSchema], default: [] })
messages?: Message[];
}
export const UserSchema = SchemaFactory.createForClass(User);

NestJS Mongoose schema definition

I have class, with nested object where I need only one field required and then all other keys are undefined and unlimited with string type, how could I write it in TypeScript?
I have tried this logic:
#Schema({ _id: false })
class Translations extends mongoose.Document {
#Prop({ required: true })
en: string;
#Prop()
[key: string]: string;
}
but mongoose complains about it
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import { Document } from 'mongoose';
#Schema()
export class Translation {
#Prop({required: true})
en: string;
#Prop()
[key: string]: string;
}
export const TranslationSchema = SchemaFactory.createForClass(Translation);

#Prop decorator for specific nested objects in array

I have an array of objects that returns in JSON, each object looks like that:
{
"reviewId": "f1a0ec26-9aca-424f-8b05-cff6aa3d2337",
"authorName": "Some name",
"comments": [
{
"userComment": {
"text": "\tAmazing!",
"lastModified": {
"seconds": "1659685904",
},
"starRating": 5,
"reviewerLanguage": "en",
}
},
{
"developerComment": {
"text": "Thank you.",
"lastModified": {
"seconds": "1659688852",
}
}
}
]
}
I'm trying to create a Schema for this specific object, but I have some issues and I cannot understand how to create it, this is what i've done so far:
import mongoose, { Document, Mongoose } from 'mongoose';
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
export type ReviewDocument = Review & Document;
#Schema()
export class Review {
#Prop({ type: String, required: true })
reviewId: string;
#Prop({ type: String })
authorName: string;
#Prop({ type: mongoose.Schema.Types.Array })
comments: Comments[];
}
#Schema()
export class Comments {
#Prop({ type: mongoose.Schema.Types.ObjectId })
userComment: UserComment;
#Prop({ type: mongoose.Schema.Types.ObjectId })
developerComment: DeveloperComment;
}
#Schema()
export class UserComment {
#Prop({ type: String })
text: string;
#Prop({ type: String })
originalText: string;
#Prop({ type: String })
lastModified: string;
#Prop({ type: Number })
starRating: number;
#Prop({ type: String })
reviewerLanguage: string;
}
#Schema()
export class DeveloperComment {
#Prop({ type: String })
text: string;
#Prop({ type: String })
lastModified: string;
}
export const ReviewSchema = SchemaFactory.createForClass(Review);
It gives me an error:
/.../rtr-backend/src/schemas/reviews.schema.ts:21
userComment: UserComment;
^
ReferenceError: Cannot access 'UserComment' before initialization
at Object.<anonymous> (/.../rtr-backend/src/schemas/reviews.schema.ts:21:18)
at Module._compile (node:internal/modules/cjs/loader:1120:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1174:10)
at Module.load (node:internal/modules/cjs/loader:998:32)
at Function.Module._load (node:internal/modules/cjs/loader:839:12)
at Module.require (node:internal/modules/cjs/loader:1022:19)
at require (node:internal/modules/cjs/helpers:102:18)
at Object.<anonymous> (/.../rtr-backend/src/reviews/reviews.module.ts:6:1)
at Module._compile (node:internal/modules/cjs/loader:1120:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1174:10)
What is best practice?
There are a few things to notice when you define a mongoose schema:
The schema types of primitive properties (e.g. string, number) are automatically inferred, so you don't need to explicitly specify { type: String} in the decorator.
In order to preserve the nested schema validation, each object has to have its own schema or be defined using the raw schema definition.
For each nested schema please mind the default properties created by mongoose, such as timestamps, _id, __v, and so on. You could manipulate them by passing options object in the #Schema() decorator.
Here is the schema definition for your use case:
import { Prop, raw, Schema, SchemaFactory } from '#nestjs/mongoose';
import { Document, Types } from 'mongoose';
#Schema({ _id: false })
class UserComment {
#Prop({ required: true })
text: string;
#Prop(raw({ seconds: { type: Number } }))
lastModified: Record<string, number>;
#Prop({ required: true })
starRating: number;
#Prop({ required: true })
reviewerLanguage: string;
}
const UserCommentSchema = SchemaFactory.createForClass(UserComment);
#Schema({ _id: false })
class DeveloperComment {
#Prop({ required: true })
text: string;
#Prop(raw({ seconds: { type: Number } }))
lastModified: Record<string, number>;
}
const DeveloperCommentSchema = SchemaFactory.createForClass(DeveloperComment);
#Schema({ _id: false })
class Comment {
#Prop({ type: UserCommentSchema })
userComment?: UserComment;
#Prop({ type: DeveloperCommentSchema })
developerComment?: DeveloperComment;
}
const CommentSchema = SchemaFactory.createForClass(Comment);
#Schema({ timestamps: true, versionKey: false })
export class Review {
_id: Types.ObjectId;
createdAt: Date;
updatedAt: Date;
#Prop({ unique: true, required: true })
reviewId: string;
#Prop({ required: true })
authorName: string;
#Prop({ type: [CommentSchema], default: [] })
comments: Comment[];
}
export type ReviewDocument = Review & Document;
export const ReviewSchema = SchemaFactory.createForClass(Review);
PS: In this documentation page you could find plenty of use cases: https://docs.nestjs.com/techniques/mongodb
I think you need to first define the schemas in the order of their dependencies, like this:
import mongoose, { Document, Mongoose } from 'mongoose';
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
export type ReviewDocument = Review & Document;
#Schema()
export class UserComment {
#Prop({ type: String })
text: string;
#Prop({ type: String })
originalText: string;
#Prop({ type: String })
lastModified: string;
#Prop({ type: Number })
starRating: number;
#Prop({ type: String })
reviewerLanguage: string;
}
#Schema()
export class DeveloperComment {
#Prop({ type: String })
text: string;
#Prop({ type: String })
lastModified: string;
}
#Schema()
export class Comments {
#Prop({ type: mongoose.Schema.Types.ObjectId })
userComment: UserComment;
#Prop({ type: mongoose.Schema.Types.ObjectId })
developerComment: DeveloperComment;
}
#Schema()
export class Review {
#Prop({ type: String, required: true })
reviewId: string;
#Prop({ type: String })
authorName: string;
#Prop({ type: mongoose.Schema.Types.Array })
comments: Comments[];
}
export const ReviewSchema = SchemaFactory.createForClass(Review);

MissingSchemaError in Nestjs

hello everyone i'm not able specify relation to another model. when i add a relation it's showing me this error
Book Model
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import { Document } from 'mongoose';
export type BookDocument = Book & Document;
#Schema({ timestamps: true, collection: 'books' })
export class Book {
#Prop({ type: String, required: true })
name: string;
#Prop({ type: String, required: true })
author: string;
#Prop({ type: String, required: true })
bookType: string;
}
export const BooksSchema = SchemaFactory.createForClass(Book);
BookLend Model
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import { Schema as mongooseSchema, Document } from 'mongoose';
import { Book } from '../../books/enitiy/book.model';
import { IsNotEmpty } from 'class-validator';
export type BookLendDocument = BookLend & Document;
#Schema({ timestamps: true })
export class BookLend {
#IsNotEmpty()
#Prop({ type: mongooseSchema.Types.ObjectId, ref: 'books', required: true })
bookId: Book;
#IsNotEmpty()
#Prop({ type: String, required: true })
name: string;
#IsNotEmpty()
#Prop({ type: String, required: true })
returnDate: string;
#Prop({ type: String })
returnedOn: string;
#IsNotEmpty()
#Prop({ type: String, required: true })
status: string;
}
export const BookLendSchema = SchemaFactory.createForClass(BookLend);
i'm referring the books objectID to booklend booksID , when i use below code i'm getting error MissingSchemaError: Schema hasn't been registered for model "books".
const allBookLendDetails = await this.bookLend
.find()
.populate('bookId')
.exec();
hi guy's I fixed it by importing the BooksSchema into BookLend Module file
here ref code:-
import { Module } from '#nestjs/common';
import { BookLendService } from './book-lend.service';
import { BookLendController } from './book-lend.controller';
import { MongooseModule } from '#nestjs/mongoose';
import { BookLendSchema } from './entities/book-lend.entity';
import { CommonService } from '../common-service/common-service.service';
import { BooksSchema } from '../books/enitiy/book.model';
#Module({
imports: [
MongooseModule.forFeature([
{ name: 'booklend', schema: BookLendSchema },
{ name: 'books', schema: BooksSchema },
]),
],
controllers: [BookLendController],
providers: [BookLendService, CommonService],
})
export class BookLendModule {}
In the service file, you need to inject the model
constructor(
#InjectModel('booklend') private readonly bookLend: Model<BookLend>,
#InjectModel('books') private readonly books: Model<Book>
) {}

NestJS Mongo references not saved properly

I am working on a NestJS backend with Mongo but I am experiencing difficulties with the mongo references.
Let me explain the situation a bit more.
I have class called SystemInformation that contain fields like when was the object created or by who.
All the other schema of the application extend this class.
The field "createdBy" is a references to the User schema (that also extend SystemInformation).
When I am saving an object the payload contain the id of the user who created the record.
But when I look at the mongo database from Compass I see the field as a string, but never as a ref with the official format which look like :
{ "$ref" : <value>, "$id" : <value>, "$db" : <value> }
Here are the relevant part of the code is am using :
This is the system class and schema :
#ObjectType()
#Schema()
class SystemContent {
#Field()
#Prop({ required: true })
createdAt: number;
#Field()
#Prop({ default: 0 })
updatedAt: number;
#Field()
#Prop({ required: true, type: mongoose.Schema.Types.ObjectId, ref: 'User' })
createdBy: string;
#Field()
#Prop({ default: '' })
updatedBy: string;
}
#ObjectType()
#Schema()
export class SystemInformation {
#Field()
#Prop({ required: true })
system: SystemContent;
}
The User class as example of my extend implementation:
#Schema()
export class User extends SystemInformation {
id: string;
#Prop({ required: true, unique: true })
username: string;
#Prop({ required: true, unique: true })
email: string;
#Prop({ required: true })
hash: string;
#Prop({ default: false })
verified: boolean;
#Prop({ default: false })
enabled: boolean;
#Prop({ default: 0 })
bruteforce: number;
}
export const UserSchema = SchemaFactory.createForClass(User);
export type UserDocument = User & Document;
The payload and function that save to mongo is :
const payload = {
...
system: {
createdBy: '601c12060164023d59120cf43',
createdAt: 0,
},
};
const result = await new this.model(payload).save();
I wonder what I am doing wrong, could you guys please help me ?
but never as a ref with the official format which look like
mongoose dosnt use such an format, mongoose references work by saving the id directly as the type that it is on the target schema (objectid for objectid, string for string), and to look up which db an id is assigned, it probably uses the model on what connection & db it is created on
PS: typegoose references can be expressed with public property: Ref<TargetClass>
PPS: the official typegoose type to combine TargetClass and Document is called DocumentType<TargetClass>