Mongoose typings, id property not available - node.js

I used the following code for my mongoose TypeScript model.
import mongoose = require('mongoose')
export interface PieceInterface extends mongoose.Document {
date: Date
summary: string
source: string
link: string
}
export const PieceSchema = new mongoose.Schema({
date: { type: Date, default: new Date(), required: true },
summary: { type: String, default: 'summary', required: true },
source: { type: String, default: 'source', required: true },
link: { type: String, default: '#', required: true }
})
export const Piece = mongoose.model<PieceInterface>('Piece', PieceSchema)
Now, when I use Piece.find({}).then(x => {...}), x is a Promise<PieceInterface[]> as expected.
However, I am not able to get
Piece.find({}).then(x => {
console.log(x[0].id)
})
to compile properly, it always tells me error TS2339: Property 'id' does not exist on type 'PieceInterface'.
Since, PieceInterface extends mongoose.Document, I looked...
interface Document extends MongooseDocument, NodeJS.EventEmitter, ModelProperties
And then,
class MongooseDocument implements MongooseDocumentOptionals
And finally this,
interface MongooseDocumentOptionals {
id?: string;
}
And in my mind, it should work. I am able to use variables provided by MongooseDocument and ModelProperties, but not MongooseDocumentOptionals.
Anything I'm missing?
References: mongoose.d.ts from GitHub
Definition of document
Definition of MongooseDocument
Definition of MongooseDocumentOptionals

Default field name for the ID in Mongo is _id not id, so you should do:
Piece.find({}).then(x => {
console.log(x[0]._id)
})

my friend,
Use this mechanism,
import mongoose = require('mongoose')
export interface PieceInterface extends mongoose.Document {
id : mongoose.Types.ObjectId //Add this line
date: Date
summary: string
source: string
link: string
}
export const PieceSchema = new mongoose.Schema({
date: { type: Date, default: new Date(), required: true },
summary: { type: String, default: 'summary', required: true },
source: { type: String, default: 'source', required: true },
link: { type: String, default: '#', required: true }
})
export const Piece = mongoose.model<PieceInterface>('Piece', PieceSchema)

Related

MongoDB Typescript Error "Type 'ObjectId' is not assignable to type 'never'

As one of my GraphQL resolvers, I have this function in which it adds an Artist ID to a user's Liked Artists Object ID array. The code is as follows:
async likeArtist(_parent, args, _context, _info) {
await User.findOneAndUpdate(
{ _id: args.userID },
{ $push: { likedArtists: new ObjectId(args.artistID as string) } },
{
new: true,
runValidators: true,
}
);
return true;
},
When I try it out on the actual website, it seems to be working fine. The weird thing is that the line where $push is is throwing an error, specifically at "likedArtists" saying:
... gave the following error.
Type 'ObjectId' is not assignable to type 'never'
This is the User's Schema
import mongoose from "mongoose";
const UserSchema = new mongoose.Schema({
image: String,
email: String,
posts: Array,
likedPosts: Array,
likedArtists: Array,
balance: String,
notifications: Array,
tutorial: {
type: Boolean,
default: true,
},
name: String,
age: String,
country: String,
birthday: String,
phone: String,
newUser: Boolean,
notifRead: Boolean,
artLevel: String,
artStyles: Array,
artKinds: Array,
userBio: String,
// More to come
});
export default mongoose.models.User || mongoose.model("User", UserSchema);
How do I remove the typescript error? It's getting in the way when I try to npm run build.
I think you may need to add typing info for your model. An example taken from the blog:
import mongoose, { Schema, Document } from 'mongoose';
export interface IUser extends Document {
email: string;
firstName: string;
lastName: string;
}
const UserSchema: Schema = new Schema({
email: { type: String, required: true, unique: true },
firstName: { type: String, required: true },
lastName: { type: String, required: true }
});
// Export the model and return your IUser interface
export default mongoose.model<IUser>('User', UserSchema);
Reference: https://tomanagle.medium.com/strongly-typed-models-with-mongoose-and-typescript-7bc2f7197722

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.

nestjs mongoose schema with embedded objects without embedded documents

I am creating a schema that included embedded objects (not documents). I do not need embedded documents because the embedded objects do not exist independently and I do not need associated object IDs. This is not well documented in the Nest documentation but I came up with this which is working for the most part:
const pointType = {
coordinates: { type: [Number], required: true },
type: { type: ['Point'], required: true },
}
const locationType = {
name: String,
address: addressType,
geolocation: { pointType },
}
const timedLocationType = {
timestamp: { type: Date, required: true, default: Date.now() },
geolocation: { type: pointType, required: true }, // <<---- Problem is here with the 'required' attribute
}
export class TripLocation {
name: string
geolocation: Point
address?: Address
}
export class TimedLocation {
time: Date
geolocation: Point
}
#Schema()
export class Trip {
#Prop({ type: locationType, required: true })
start: TripLocation
#Prop({ type: locationType, required: true })
end: TripLocation
#Prop({ type: [timedLocationType], default: [] }) // <<-- Problem is here
route: TimedLocation[]
}
This works except for having the timedLocation array. Mongoose does not seem to like having the route property as an array. As is, the code throws this error:
.../node_modules/mongoose/lib/schema.js:1001
throw new TypeError(`Invalid schema configuration: \`${name}\` is not ` +
^
TypeError: Invalid schema configuration: `True` is not a valid type at path `geolocation.required`. See .../mongoose-schematypes for a list of valid schema types.
at Schema.interpretAsType (.../node_modules/mongoose/lib/schema.js:1001:11)
If I remove the required attribute on the timedLocationType.geolocation, the error goes away. Alternatively, if I turn the route property from an array to a single object, and keep the required` attribute, the error also goes away:
#Prop({ type: timedLocationType })
route: TimedLocation

How to solve circular dependency in model definition in node?

I'm following MVC architecture using express and mongoose and I came upon an issue with circular-dependency. The code itself is written in ES6.
I have these two particular models (keep in mind that I obscured these models as much a possible):
Destination model, which contains information about all available rooms:
// destination.model.js
import mongoose, { Schema } from 'mongoose';
import Booking from './booking.model'; // eslint detect dependency cycle here
import Room from './room.model';
const DestinationSchema = new Schema({
id: { type: Number, required: true },
name: { type: String, required: true, max: 100 },
description: { type: String, required: false },
rooms: [Room.schema]
});
DestinationSchema.statics.getAvailableRooms = async function (startDate, endDate) {
const bookings = await Booking.find({ 'room._id': room._id });
// do something with these bookings
};
export default mongoose.model('Destination', DestinationSchema);
and Booking model, which is in relation many to one with Destination.
// booking.model.js
import mongoose, { Schema } from 'mongoose';
import Destination from './destination.model';
import Room from './room.model';
const BookingSchema = new Schema({
id: { type: Number, required: true },
client: { type: String, required: true },
startDate: { type: Date, default: '' },
endDate: { type: Date, default: '' },
room: { type: Room.schema, required: false },
destination: Destination.schema
});
export default mongoose.model('Booking', BookingSchema);
Main issue:
ESLint detects dependency cycle in destination model (and in booking model) - which is present. The reason for that, is that I have a static method in Destination model, which looks over all Bookings (and possibly might call a static method in the future).
My question (or actually looking for an advice) is, how do I handle this issue? I come from Ruby on Rails background, so I'm really used to having both instance and static methods defined in a model, with single file.
I don't want to separate methods into another file and I would like to keep them in single file - is that possible in any way or should I really go for file separation?
Cheers
I think you should model booking schema like:
const BookingSchema = new Schema({
id: { type: Number, required: true },
client: { type: String, required: true },
startDate: { type: Date, default: '' },
endDate: { type: Date, default: '' },
room: { type: Room.schema, required: false },
destination: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Destination',
},
});

Mongoose use only createdAt timestamp

I have the following message schema in mongoose:
var messageSchema = mongoose.Schema({
userID: { type: ObjectId, required: true, ref: 'User' },
text: { type: String, required: true }
},
{
timestamps: true
});
Is there anyway to ignore the updatedAt timestamp? Messages won't be updated so updatedAt will be wasted space
Maybe even better with Mongoose v5 is to do the following;
const schema = new Schema({
// Your schema...
}, {
timestamps: { createdAt: true, updatedAt: false }
})
Edit I've amended the answer to reflect the better option to use the default as per #JohnnyHK
You can handle this yourself by declaring the createdAt (or whatever you want to call it) in your schema:
mongoose.Schema({
created: { type: Date, default: Date.now }
...
Alternatively we can also update values on new document in a pre save hook:
messageSchema.pre('save', function (next) {
if (!this.created) this.created = new Date;
next();
})
Along those lines is also the flag isNew which you can use to check if a document is new.
messageSchema.pre('save', function (next) {
if (this.isNew) this.created = new Date;
next();
})
Older topic but there may be a better option depending on your schema...
If you're sticking with the default of having mongodb/mongoose auto-gen _id, there's already a timestamp built in. If all you need is "created" and not "updated" just use...
document._id.getTimestamp();
From MongoDB docs here...
ObjectId.getTimestamp()
And Here... stackoverflow
Mongoose timestamp interface has these optional fields.
interface SchemaTimestampsConfig {
createdAt?: boolean | string;
updatedAt?: boolean | string;
currentTime?: () => (Date | number);
}
We can pass the boolean for the field we want(createdAt: true and updatedAt: true will add both fields).
We can use the currentTime function to overwrite the date format.
example:
import mongoose from 'mongoose';
const { Schema } = mongoose;
const annotationType = ['NOTES', 'COMMENTS'];
const referenceType = ['TASKS', 'NOTES'];
const AnnotationSchema = new Schema(
{
sellerOrgId: {
type: String,
required: true,
},
createdById: {
type: String,
required: true,
},
annotationType: {
type: String,
enum: annotationType,
},
reference: {
id: { type: String, index: true },
type: {
type: String,
enum: referenceType,
},
},
data: Schema.Types.Mixed,
},
{ timestamps: { createdAt: true },
);
const AnnotationModel = mongoose.models.annotation || mongoose.model('annotation', AnnotationSchema);
export { AnnotationModel, AnnotationSchema };

Resources