2

I am wondering what's the best way to achieve strongly typed static functions for Mongoose Schemas when using TypeScript.

I have a solution right now, but it feels messy and adds a lot of extra code if it has to be done for each schema. I will present it at the bottom of this post, but will show the current setup first:

I have the following interfaces:

interfaces/Player.ts

export interface Player {
  name: string
  password: string
}

export interface PlayerDocument extends Player, Document {
   // statics
   findByName(name: string): any
};

The reason I have two interfaces here is because I want to be able to use the Player interface without the Mongoose-bindings as well other places in the app. PlayerDocument here represent the schema-ready interface, which we will use here:

schemas/Player.ts


const playerSchema = new mongoose.Schema({
  name:  { type: String, required: true, unique: true },
  password: String,
});

// Not using arrow functions because they prevent binding this. 
// See mongoose-docs
playerSchema.statics.findByName = function(name: string) {
  return this.find({ name: new RegExp(name, 'i') });
}

interface test {
  findByName(name: string): any
}

export default mongoose.model<PlayerDocument>('Player', playerSchema);

The problem here, is that whenever I am now using the schema in other places of the app, I am not getting the static function findByName(name: string): any present in the type.

app.ts

import PlayerSchema from './schemas/Player';
const test = async () => {
    const x = await PlayerSchema.findByName('Erlend');
    console.log(x);
}

This gives:

code hint not appearing

My solution, which seems hacky

I managed to solve the issue by creating the following combined type:

schemas/Player.ts

interface test {
    findByName(name: string): any
}
type something = mongoose.Model<PlayerDocument> & test;
export default (mongoose.model<PlayerDocument>('Player', playerSchema)) as something;

Aand finally:

code hint appearing

But as explained above, I feel like there should be a better way.. Ideas?

Erlend Ellingsen
  • 319
  • 1
  • 2
  • 11

1 Answers1

1

Move your model typing to your interfaces:

export interface Player {
  name: string;
  password: string;
}

export interface PlayerModel extends Model<Player & Document> {
   findByName: (name: string) => any;
}

Now when you create the model, give it two types: the document, and the model:

const playerModel = model<Player, PlayerStatics>('Player', playerSchema);

playerModel.findByName('foo');

See also:

Ryan W.
  • 41
  • 4