How do I add static methods to Mongoose model in Typescript for Mongoose 5.11?

Viewed 702

In Mongoose 5.10, I used the following pattern for adding static methods to my Mongoose models:

import { Schema, Document, Model, Connection } from "mongoose";
import { v4 } from "uuid";

export interface IFoo extends Document {
  id: string;
  name: string;
}

export interface IFooModel extends Model<IFoo> {
  findOneOrCreate({ name }: { name: string }): Promise<IFoo>;
}

const fooSchema = new Schema({
  id: {
    type: String,
    required: true,
    default: () => `a-${v4()}`,
  },
  name: {
    type: String,
    required: true,
    unique: true,
  },
});

fooSchema.static({
  findOneOrCreate: async function (
    this: IFooModel,
    { name }: { name: string }
  ): Promise<IFoo> {
    let foo = await this.findOne({ name });
    if (!foo) {
      foo = await this.create({ name });
    }
    return foo;
  },
});

export default function (db: Connection): IFooModel {
  return db.model<IFoo, IFooModel>("Foo", fooSchema);
}

In Mongoose 5.11, Mongoose includes its own Typescript type definitions; overriding what was defined in @types/mongoose. These updates remove the second type parameter to mongoose.model(), and results in the following type errors:

src/models/foo.ts:40:3 - error TS2741: Property 'findOneOrCreate' is missing in type 'Model<IFoo>' but required in type 'IFooModel'.

40   return db.model<IFoo, IFooModel>("Foo", fooSchema);
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  src/models/foo.ts:10:3
    10   findOneOrCreate({ name }: { name: string }): Promise<IFoo>;
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    'findOneOrCreate' is declared here.

src/models/foo.ts:40:19 - error TS2558: Expected 1 type arguments, but got 2.

40   return db.model<IFoo, IFooModel>("Foo", fooSchema);
                     ~~~~~~~~~~~~~~~


Found 2 errors.

With the new Mongoose 5.11 type definitions, what's the proper way to to add these static methods to my model?

1 Answers

The key is to set the generic types in the Schema constructor like this :

const fooSchema = new Schema<IFoo, IFooModel>({
  id: {
    type: String,
    required: true,
    default: () => `a-${v4()}`,
  },
  name: {
    type: String,
    required: true,
    unique: true,
  },
});

I also had a few problems with the document type not representing the returned model type so I would suggest creating a new union type like this and than using the types you need accordingly:

export interface IFoo {
  id: string;
  name: string;
}

type IFooDocument = IFoo & Document;
Related