Skip to content

docs: emphasize automatic type inference in TypeScript intro and statics/methods, remove duplicated statics.md #15421

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 41 additions & 7 deletions docs/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,46 @@ This guide describes Mongoose's recommended approach to working with Mongoose in

To get started with Mongoose in TypeScript, you need to:

1. Create an interface representing a document in MongoDB.
2. Create a [Schema](guide.html) corresponding to the document interface.
3. Create a Model.
4. [Connect to MongoDB](connections.html).
1. Create a [Schema](guide.html).
2. Create a Model.
3. [Connect to MongoDB](connections.html).

```typescript
import { Schema, model, connect } from 'mongoose';

// 1. Create a Schema corresponding to the document interface.
const userSchema = new Schema({
name: { type: String, required: true },
email: { type: String, required: true },
avatar: String
});

// 2. Create a Model.
const User = model('User', userSchema);

run().catch(err => console.log(err));

async function run() {
// 3. Connect to MongoDB
await connect('mongodb://127.0.0.1:27017/test');

const user = new User({
name: 'Bill',
email: '[email protected]',
avatar: 'https://i.imgur.com/dM7Thhn.png'
});
await user.save();

const email: string = user.email;
console.log(email); // '[email protected]'
}
```

## Using Generics

By default, Mongoose automatically infers the shape of your documents based on your schema definition.
However, if you modify your schema after your `new Schema()` call (like with plugins) then Mongoose's inferred type may be incorrect.
For cases where Mongoose's automatic schema type inference is incorrect, you can define a raw document interface that tells Mongoose the type of documents in your database as follows.

```typescript
import { Schema, model, connect } from 'mongoose';
Expand Down Expand Up @@ -67,8 +103,6 @@ const user: HydratedDocument<IUser> = new User({
});
```

## ObjectIds and Other Mongoose Types

To define a property of type `ObjectId`, you should use `Types.ObjectId` in the TypeScript document interface. You should use `'ObjectId'` or `Schema.Types.ObjectId` in your schema definition.

```ts
Expand Down Expand Up @@ -106,4 +140,4 @@ However, before you do, please [open an issue on Mongoose's GitHub page](https:/

## Next Up

Now that you've seen the basics of how to use Mongoose in TypeScript, let's take a look at [statics in TypeScript](typescript/statics-and-methods.html).
Now that you've seen the basics of how to use Mongoose in TypeScript, let's take a look at [methods in TypeScript](typescript/statics-and-methods.html).
132 changes: 43 additions & 89 deletions docs/typescript/statics-and-methods.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,37 @@
# Statics and Methods in TypeScript
# Statics in TypeScript

You can define instance methods and static functions on Mongoose models.
With a little extra configuration, you can also register methods and statics in TypeScript.

## Methods

To define an [instance method](../guide.html#methods) in TypeScript, create a new interface representing your instance methods.
You need to pass that interface as the 3rd generic parameter to the `Schema` constructor **and** as the 3rd generic parameter to `Model` as shown below.
To use Mongoose's automatic type inference to define types for your [statics](../guide.html#statics) and [methods](../guide.html#methods), you should define your methods and statics using the `methods` and `statics` schema options as follows.
Do **not** use the `Schema.prototype.method()` and `Schema.prototype.static()` functions, because Mongoose's automatic type inference system cannot detect methods and statics defined using those functions.

```typescript
import { Model, Schema, model } from 'mongoose';

interface IUser {
firstName: string;
lastName: string;
}

// Put all user instance methods in this interface:
interface IUserMethods {
fullName(): string;
}

// Create a new Model type that knows about IUserMethods...
type UserModel = Model<IUser, {}, IUserMethods>;

// And a schema that knows about IUserMethods
const schema = new Schema<IUser, UserModel, IUserMethods>({
firstName: { type: String, required: true },
lastName: { type: String, required: true }
});
schema.method('fullName', function fullName() {
return this.firstName + ' ' + this.lastName;
});

const User = model<IUser, UserModel>('User', schema);
const userSchema = new mongoose.Schema(
{ name: { type: String, required: true } },
{
methods: {
updateName(name: string) {
this.name = name;
return this.save();
}
},
statics: {
createWithName(name: string) {
return this.create({ name });
}
}
}
);
const UserModel = mongoose.model('User', userSchema);

const user = new User({ firstName: 'Jean-Luc', lastName: 'Picard' });
const fullName: string = user.fullName(); // 'Jean-Luc Picard'
const doc = new UserModel({ name: 'test' });
// Compiles correctly
doc.updateName('foo');
// Compiles correctly
UserModel.createWithName('bar');
```

## Statics
## With Generics

We recommend using Mongoose's automatic type inference where possible, but you can use `Schema` and `Model` generics to set up type inference for your statics and methods.
Mongoose [models](../models.html) do **not** have an explicit generic parameter for [statics](../guide.html#statics).
If your model has statics, we recommend creating an interface that [extends](https://www.typescriptlang.org/docs/handbook/interfaces.html) Mongoose's `Model` interface as shown below.

Expand All @@ -51,78 +42,41 @@ interface IUser {
name: string;
}

interface UserModel extends Model<IUser> {
interface UserModelType extends Model<IUser> {
myStaticMethod(): number;
}

const schema = new Schema<IUser, UserModel>({ name: String });
const schema = new Schema<IUser, UserModelType>({ name: String });
schema.static('myStaticMethod', function myStaticMethod() {
return 42;
});

const User = model<IUser, UserModel>('User', schema);
const User = model<IUser, UserModelType>('User', schema);

const answer: number = User.myStaticMethod(); // 42
```

Mongoose does support auto typed static functions now that it is supplied in schema options.
Statics functions can be defined as following:

```typescript
import { Schema, model } from 'mongoose';

const schema = new Schema(
{ name: String },
{
statics: {
myStaticMethod() {
return 42;
}
}
}
);

const User = model('User', schema);

const answer = User.myStaticMethod(); // 42
```

## Both Methods and Statics

Below is how you can define a model that has both methods and statics.
You should pass methods as the 3rd generic param to the `Schema` constructor as follows.

```typescript
import { Model, Schema, HydratedDocument, model } from 'mongoose';
import { Model, Schema, model } from 'mongoose';

interface IUser {
firstName: string;
lastName: string;
}

interface IUserMethods {
fullName(): string;
name: string;
}

interface UserModel extends Model<IUser, {}, IUserMethods> {
createWithFullName(name: string): Promise<HydratedDocument<IUser, IUserMethods>>;
interface UserMethods {
updateName(name: string): Promise<any>;
}

const schema = new Schema<IUser, UserModel, IUserMethods>({
firstName: { type: String, required: true },
lastName: { type: String, required: true }
const schema = new Schema<IUser, Model<IUser>, UserMethods>({ name: String });
schema.method('updateName', function updateName(name) {
this.name = name;
return this.save();
});
schema.static('createWithFullName', function createWithFullName(name: string) {
const [firstName, lastName] = name.split(' ');
return this.create({ firstName, lastName });
});
schema.method('fullName', function fullName(): string {
return this.firstName + ' ' + this.lastName;
});

const User = model<IUser, UserModel>('User', schema);

User.createWithFullName('Jean-Luc Picard').then(doc => {
console.log(doc.firstName); // 'Jean-Luc'
doc.fullName(); // 'Jean-Luc Picard'
});
const User = model('User', schema);
const doc = new User({ name: 'test' });
// Compiles correctly
doc.updateName('foo');
```
82 changes: 0 additions & 82 deletions docs/typescript/statics.md

This file was deleted.

4 changes: 2 additions & 2 deletions docs/typescript/virtuals.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ interface UserVirtuals {
fullName: string;
}

type UserModel = Model<UserDoc, {}, {}, UserVirtuals>; // <-- add virtuals here...
type UserModelType = Model<UserDoc, {}, {}, UserVirtuals>; // <-- add virtuals here...

const schema = new Schema<UserDoc, UserModel, {}, {}, UserVirtuals>({ // <-- and here
const schema = new Schema<UserDoc, UserModelType, {}, {}, UserVirtuals>({ // <-- and here
firstName: String,
lastName: String
});
Expand Down