In the official documentation for using mongoose with typescript, in regards to extending the mongoose.Document type when providing a type definition, it states:
This approach works, but we recommend your document interface not extend Document. Using extends Document makes it difficult for Mongoose to infer which properties are present on query filters, lean documents, and other cases.
We recommend your document interface contain the properties defined in your schema and line up with what your documents look like in MongoDB. Although you can add instance methods to your document interface, we do not recommend doing so.
With regards to the last line of the second paragraph, if it is recommended not to include instance/document methods in the document interface — how should we provide this information? Without defining these methods on the document interface, typescript throws an error that Property <insert method name> does not exist on type <insert document types>.
For example, consider the following definition for a user:
// user.model.ts
import { model, Schema } from 'mongoose';
import { UserDoc } from '../types/user.d.ts';
const userSchema = new Schema<UserDoc, Model<UserDoc>, UserDoc>({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
email: { type: String, required: true, unique: true, lowercase: true },
password: { type: String, required: true, trim: true }
});
userSchema.virtual('fullName').get(function (this: UserDoc) {
return `${this.firstName} ${this.lastName}`
});
userSchema.method('getFullName', function () {
return `${this.firstName} ${this.lastName}`
})
export default model<UserDoc, Model<UserDoc>>('User', userSchema)
The UserDoc interface is defined as follows:
export interface UserDoc {
firstName: string;
lastName: string;
email: string;
password: string;
}
Without explicitly defining the fullName virtual property or the getFullName instance method on the UserDoc interface, neither can be accessed.
For example, consider the following express route handler to register a new user:
import { RequestHandler } from 'express';
import User from '../models/user.model.ts'; // The model as defined in the above userSchema
const registerUserHandler: RequestHandler = async (req, res, next) => {
const { firstName, lastName, email, password } = req.body;
if (await User.exists({ email })) {
return res.status(400).json('Email already exists')
}
const newUser = await User.create({ firstName, lastName, email, password });
console.log(newUser.fullName) // This will cause typescript error
console.log(newUser.getFullName()) // This will cause typescript error
return res.status(201).json('User created')
}
However, if the UserDoc interface is extended to include the fullName virtual and the getFullName method, typescript won't error and will allow access.
When looking at the official documentation for static methods, it states that you must include these and extend the mongoose.Model<T> interface.
So, if we aren't supposed to put these on the document interface, how are we supposed to define them so that typescript can allow access?