Hey i have a schema for user with an unique email:
#Schema()
export class User {
#Prop()
firstName!: string;
#Prop()
lastName!: string;
#Prop({
unique: true,
})
email!: string;
#Prop({ nullable: true })
password?: string;
}
But now i want to extend this. I wanna have Groups with their own users. I would create a collection of groups and add their id to the users like:
#Schema()
export class User {
#Prop()
groupId: string;
#Prop()
firstName!: string;
...
}
For each group the email should be unique. That means in the collection of users there could be duplicate emails but they should be unique by group which is named Unique Compound Index i guess.
How do i set this up in NestJS?
Looks like its not possible to achieve it solely by use of the #nestjs/mongoose decorators, however its possible to declare index by use of SchemaFactory
#Schema()
export class User {
#Prop()
groupId: string;
#Prop()
firstName!: string;
...
}
export const UserSchema = SchemaFactory.createForClass(User);
UserSchema.index({ groupId: 1, firstName: 1 }, { unique: true });
Then you must do either create migration to create this index or enable auto-index feature
#Schema({
autoIndex: true, // <--
})
export class User {
#Prop()
groupId: string;
#Prop()
firstName!: string;
...
}
Related
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
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);
I want to get then enteries from paris air quality, but the thing is that the mongoose schema is nested and I tried to make multiple schemas separated but I don't know wheter it's correct or not.
This is the schema file schemas/air-quality.schema.ts
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import { Document } from 'mongoose';
export type AirQualityDocument = AirQuality & Document;
#Schema()
export class Pollution {
#Prop()
ts: string;
#Prop()
aqius: number;
#Prop()
mainus: string;
#Prop()
aqicn: number;
#Prop()
maincn: string;
}
#Schema()
export class Result {
#Prop({ type: Pollution })
pollution: Pollution;
}
#Schema()
export class AirQuality {
#Prop({ type: Result })
result: Result;
#Prop()
datetime: string;
}
export const AirQualitySchema = SchemaFactory.createForClass(AirQuality);
And the result I am getting from mongo is this
PS: it contains multiple entries of the same schema
[
{
"_id": "62dd1b2e744e6bf8cdbcfabd",
"result": {
"_id": "62dd1b2e744e6bf8cdbcfabe"
},
"datetime": "24/07/2022, 11:13:02",
"__v": 0
},
...
]
I don't if the mistake is with the schema or I just need to use autopopulate or something
Im using nestjs with mongoose. I try to do a simple find query
this.challengesModel.find({ createdBy: userId })
where this.challengesModel is injected like this
private readonly challengesModel: Model<Challenge>
but it says
Type 'string' is not assignable to type 'Condition<LeanDocument<User>>'
createdBy is considered as a User object whereas I am giving it a string(userId)
How can I still keep the createdBy field as User by search only by the id?
This is my schema
#Schema()
export class Challenge extends Document {
#Prop({ type: mongoose.Schema.Types.ObjectId, ref: "User" })
createdBy: User;
#Prop()
description: string;
#Prop({ default: new Date() })
creationTime: Date;
#Prop()
video: string;
#Prop({ default: [] })
likes: string[];
#Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }] })
selectedFriends: User[];
#Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: "Reply" }] })
replies: Reply[];
}
The createdBy is saved as a unique id of the user (foreign key to the users collection)
I am trying to perform a query to find challenges that were created by a user with specific id.
If I change createdBy to be a string(id) it works, but then I don't get all the user properties, also the nest documentation suggests to create it like I did.
What should I change in order to be able to do this find without any compliation errors?
Try
this.challengesModel.find({ createdBy: userId as any })
This is caused because createdBy isn't a string type.
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>