6

I'm taking advantage of mongoose class schemas.

And using TypeScript for my Node project.

I've followed Mongoose the Typescript way...? to make sure my Model is aware of the schema I've defined, So I've auto-completion etc..

However it becomes more tricky with schema class. As written in their docs:

The loadClass() function lets you pull in methods, statics, and virtuals from an ES6 class. A class method maps to a schema method, a static method maps to a schema static, and getters/setters map to virtuals.

So my code looks something like:

interface IUser extends mongoose.Document {
  firstName: string,
  lastName: string
};

const userSchema = new mongoose.Schema({
  firstName: {type:String, required: true},
  lastName: {type:String, required: true},
});

class UserClass{
   static allUsersStartingWithLetter(letter: string){
       return this.find({...});
   }
   fullName(this: IUser){
      return `${this.firstName} ${this.lastName}`
   }
}
userSchema.loadClass(UserClass);

const User = mongoose.model<IUser>('User', userSchema);
export default User;

My goal is that TypeScript will understand that:

  1. User has a method allUsersStartingWithLetter
  2. User instance has a method fullName

In the current configuration it does not. I was not able to accomplish it myself.

YardenST
  • 5,119
  • 2
  • 33
  • 54
  • Can you tell me how you are creating new models for example? You cannot use model.create(your props), you have to use new User().save(). If you do that, it is working. (I had the same issue) – Marco Nov 19 '20 at 17:05

3 Answers3

0

Do you really need to use classes? You could accomplish this using interfaces without using classes to do it. Here's an example:

/* eslint-disable func-names */
import mongoose from 'mongoose';
export interface Foo {
  id?: string;
  name: string;
  createdAt?: Date;
  updatedAt?: Date;
}

export type FooDocument = mongoose.Document & Foo;

const fooSchema = new mongoose.Schema(
  {
    name: { type: String, required: true },
  },
  { timestamps: true }
);

fooSchema.methods.bar = function (): void {
  const foo = this as FooDocument;
  foo.name = 'bar';
};

const FooModel = mongoose.model<FooDocument>('foos', fooSchema);

export default FooModel;

This way you can use the Foo interface for methods with the inversion depedency. Them in your repository will return Foo instead of FooDocument...

Extra: If you use lean() in your database requests you return exactly the Foo interface. More information for lean here

spinkus
  • 7,694
  • 4
  • 38
  • 62
Gaspar
  • 1,515
  • 13
  • 20
0

Have you considered adding extends mongoose.Model to the UserClass?

class UserClass extends mongoose.Model<IUser> {
   static allUsersStartingWithLetter(letter: string){
       return this.find({...});
   }
   fullName(this: IUser){
      return `${this.firstName} ${this.lastName}`
   }
}
zOnNet
  • 86
  • 5
-1

Your UserClass needs to extend Model from mongoose. You seem to be missing a bit of required code to make this work for you.

From the link you shared as a reference, here's a guide that should solve your issue with complete code example.

https://stackoverflow.com/a/58107396/1919397

Samuel_NET
  • 345
  • 3
  • 9
  • No, it is not required to extend from document. See documentation: https://mongoosejs.com/docs/4.x/docs/advanced_schemas.html It should work without extending from Document. – Marco Nov 19 '20 at 16:48