mongoose

SKILL.md

Mongoose Skill (2025-2026 Edition)

This skill provides modern guidelines for using Mongoose with MongoDB, focusing on Mongoose 8.x/9.x, strict TypeScript integration, and performance optimizations relevant to the 2025 ecosystem.

🚀 Key Trends & Features (2025/2026)

  • TypeScript-First: Mongoose 8+ has superior built-in type inference. @types/mongoose is obsolete.
  • Performance: Mongoose 9 introduces architectural changes for lower overhead. Native vector search support is now standard for AI features.
  • Modern JavaScript: Full support for async/await iterators and native Promises.

📐 TypeScript Integration (The Strict Way)

Do NOT extend LengthyDocument or standard Document. Use a plain interface and let Mongoose infer the rest.

1. Define the Interface (Raw Data)

Define what your data looks like in plain JavaScript objects.

import { Types } from 'mongoose';

export interface IUser {
  name: string;
  email: string;
  role: 'admin' | 'user';
  tags: string[];
  organization?: Types.ObjectId; // Use specific type for ObjectIds
  createdAt?: Date;
}

2. Define the Schema & Model

Create the model with generic typing.

import mongoose, { Schema, model } from 'mongoose';
import { IUser } from './interfaces';

const userSchema = new Schema<IUser>({
  name: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  role: { type: String, enum: ['admin', 'user'], default: 'user' },
  tags: [String],
  organization: { type: Schema.Types.ObjectId, ref: 'Organization' }
}, {
  timestamps: true /* Automatically manages createdAt/updatedAt */
});

// ⚡ 2025 Best Practice: Avoid "extends Document" on the interface.
// Let 'model<IUser>' handle the HydratedDocument type automatically.
export const User = mongoose.models.User || model<IUser>('User', userSchema);

3. Usage (Type-Safe)

// 'user' is typed as HydratedDocument<IUser> automatically
const user = await User.findOne({ email: 'test@example.com' });

if (user) {
  console.log(user.name); // Typed string
  await user.save(); // Typed method
}

⚡ Performance Optimization

1. .lean() for Reads

Always use .lean() for read-only operations (e.g., GET requests). It skips Mongoose hydration, speeding up queries by 30-50%.

// Returns plain JS object (IUser), NOT a Mongoose document
const users = await User.find({ role: 'admin' }).lean(); 

2. Indexes

Define compound indexes for common query patterns directly in the schema.

// Schema definition
userSchema.index({ organization: 1, email: 1 }); // Optimized lookup

3. Projections

Fetch only what you need. Network I/O is often the bottleneck.

const names = await User.find({}, { name: 1 }).lean();

🛠️ Connection Management (Serverless/Edge Friendly)

For modern serverless/edge environments (like Vercel, Next.js, Remix), use a singleton pattern with caching to prevent connection exhaustion.

import mongoose from 'mongoose';

const MONGODB_URI = process.env.MONGODB_URI!;

if (!MONGODB_URI) {
  throw new Error('Please define the MONGODB_URI environment variable');
}

/**
 * Global is used here to maintain a cached connection across hot reloads
 * in development. This prevents connections growing exponentially
 * during API Route usage.
 */
let cached = (global as any).mongoose;

if (!cached) {
  cached = (global as any).mongoose = { conn: null, promise: null };
}

async function connectDb() {
  if (cached.conn) {
    return cached.conn;
  }

  if (!cached.promise) {
    const opts = {
      bufferCommands: false,
    };

    cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => {
      return mongoose;
    });
  }
  
  try {
    cached.conn = await cached.promise;
  } catch (e) {
    cached.promise = null;
    throw e;
  }

  return cached.conn;
}

export default connectDb;

📚 References

Weekly Installs
6
First Seen
Jan 25, 2026
Installed on
opencode6
codex6
gemini-cli6
github-copilot5
amp5
kimi-cli5