乐闻世界logo
搜索文章和话题

How to use Mongoose with TypeScript?

2月22日 20:06

Using Mongoose with TypeScript provides type safety, better development experience, and code hints. By using Mongoose's type definitions, errors can be caught at compile time.

Basic Type Definitions

Define Schema Types

typescript
import mongoose, { Schema, Document, Model } from 'mongoose'; // Define document interface interface IUser extends Document { name: string; email: string; age: number; createdAt: Date; } // Define Schema const userSchema: Schema = new Schema({ name: { type: String, required: true }, email: { type: String, required: true, unique: true }, age: { type: Number, min: 0 }, createdAt: { type: Date, default: Date.now } }); // Create model type interface IUserModel extends Model<IUser> { findByEmail(email: string): Promise<IUser | null>; } // Create model const User: IUserModel = mongoose.model<IUser, IUserModel>('User', userSchema);

Using Typed Models

Creating Documents

typescript
const user: IUser = new User({ name: 'John Doe', email: 'john@example.com', age: 25 }); await user.save();

Querying Documents

typescript
// Query single document const user: IUser | null = await User.findById(userId); // Query multiple documents const users: IUser[] = await User.find({ age: { $gte: 18 } }); // Use lean() to return plain objects const plainUsers = await User.find().lean();

Updating Documents

typescript
const user: IUser | null = await User.findById(userId); if (user) { user.age = 26; await user.save(); } // Use findOneAndUpdate const updatedUser: IUser | null = await User.findOneAndUpdate( { email: 'john@example.com' }, { age: 26 }, { new: true } );

Static Method Types

Define Static Methods

typescript
interface IUserModel extends Model<IUser> { findByEmail(email: string): Promise<IUser | null>; findAdults(): Promise<IUser[]>; countByAge(minAge: number): Promise<number>; } // Implement static methods userSchema.statics.findByEmail = function(email: string): Promise<IUser | null> { return this.findOne({ email }); }; userSchema.statics.findAdults = function(): Promise<IUser[]> { return this.find({ age: { $gte: 18 } }); }; userSchema.statics.countByAge = function(minAge: number): Promise<number> { return this.countDocuments({ age: { $gte: minAge } }); }; // Use static methods const user = await User.findByEmail('john@example.com'); const adults = await User.findAdults(); const count = await User.countByAge(18);

Instance Method Types

Define Instance Methods

typescript
interface IUser extends Document { name: string; email: string; age: number; getFullName(): string; isAdult(): boolean; updateAge(newAge: number): Promise<IUser>; } // Implement instance methods userSchema.methods.getFullName = function(): string { return this.name; }; userSchema.methods.isAdult = function(): boolean { return this.age >= 18; }; userSchema.methods.updateAge = async function(newAge: number): Promise<IUser> { this.age = newAge; return this.save(); }; // Use instance methods const user = await User.findById(userId); if (user) { console.log(user.getFullName()); console.log(user.isAdult()); await user.updateAge(26); }

Virtual Field Types

Define Virtual Fields

typescript
interface IUser extends Document { firstName: string; lastName: string; fullName: string; // Virtual field } const userSchema: Schema = new Schema({ firstName: { type: String, required: true }, lastName: { type: String, required: true } }); // Define virtual field userSchema.virtual('fullName').get(function(this: IUser): string { return `${this.firstName} ${this.lastName}`; }); userSchema.virtual('fullName').set(function(this: IUser, value: string): void { const parts = value.split(' '); this.firstName = parts[0]; this.lastName = parts[1]; });

Nested Document Types

Define Nested Schema

typescript
interface IAddress { street: string; city: string; state: string; zipCode: string; } interface IUser extends Document { name: string; email: string; address: IAddress; } const addressSchema: Schema = new Schema({ street: { type: String, required: true }, city: { type: String, required: true }, state: { type: String, required: true }, zipCode: { type: String, required: true } }); const userSchema: Schema = new Schema({ name: { type: String, required: true }, email: { type: String, required: true }, address: { type: addressSchema, required: true } });

Array Field Types

Define Array Types

typescript
interface IUser extends Document { name: string; tags: string[]; scores: number[]; } const userSchema: Schema = new Schema({ name: { type: String, required: true }, tags: [String], scores: [Number] });

Reference Types

Define References

typescript
interface IPost extends Document { title: string; content: string; author: mongoose.Types.ObjectId; } interface IUser extends Document { name: string; email: string; posts: mongoose.Types.ObjectId[]; } const postSchema: Schema = new Schema({ title: { type: String, required: true }, content: { type: String, required: true }, author: { type: Schema.Types.ObjectId, ref: 'User' } }); const userSchema: Schema = new Schema({ name: { type: String, required: true }, email: { type: String, required: true }, posts: [{ type: Schema.Types.ObjectId, ref: 'Post' }] }); // Use populate const user = await User.findById(userId).populate('posts');

Middleware Types

Define Middleware

typescript
import { HookNextFunction } from 'mongoose'; // Pre middleware userSchema.pre('save', function(this: IUser, next: HookNextFunction): void { this.email = this.email.toLowerCase(); next(); }); // Post middleware userSchema.post('save', function(this: IUser, doc: IUser): void { console.log('User saved:', doc.email); }); // Async middleware userSchema.pre('save', async function(this: IUser, next: HookNextFunction): Promise<void> { if (await emailExists(this.email)) { return next(new Error('Email already exists')); } next(); }); async function emailExists(email: string): Promise<boolean> { const count = await User.countDocuments({ email }); return count > 0; }

Generic Models

Create Generic Models

typescript
interface BaseModel extends Document { createdAt: Date; updatedAt: Date; } function createTimestampedModel<T extends Document>( name: string, schema: Schema ): Model<T & BaseModel> { schema.add({ createdAt: { type: Date, default: Date.now }, updatedAt: { type: Date, default: Date.now } }); schema.pre('save', function(this: T & BaseModel, next: HookNextFunction): void { this.updatedAt = new Date(); next(); }); return mongoose.model<T & BaseModel>(name, schema); } // Use generic model interface IUser extends Document { name: string; email: string; } const userSchema: Schema = new Schema({ name: { type: String, required: true }, email: { type: String, required: true } }); const User = createTimestampedModel<IUser>('User', userSchema);

Best Practices

  1. Use interfaces to define types: Define interfaces for all schemas
  2. Strict type checking: Enable TypeScript's strict mode
  3. Avoid any type: Use specific types as much as possible
  4. Use type assertions: Use type assertions when necessary
  5. Complete documentation: Add clear comments to types
  6. Test coverage: Write tests for typed models
  7. Use tools: Leverage TypeScript's type hints and autocomplete
标签:Mongoose