dist/plugins/api-database-mongoose/skills/api-database-mongoose/SKILL.md
MongoDB ODM with schemas, validation, middleware, and TypeScript support
npx skillsauth add agents-inc/skills api-database-mongooseInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Quick Guide: Use Mongoose as the ODM layer for MongoDB. Let TypeScript infer types from schema definitions instead of duplicating interfaces. Register all middleware before calling
model()-- hooks added after compilation are silently ignored. Use.lean()for any read-only query. Pass{ session }to every operation inside a transaction or enabletransactionAsyncLocalStorage. Prefersession.withTransaction()over manual commit/abort. Use127.0.0.1instead oflocalhostin connection strings (Node.js 18+ IPv6 preference causes timeouts).
<critical_requirements>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST define all middleware (pre/post hooks) BEFORE calling model() -- hooks registered after model compilation are silently ignored with no error)
(You MUST pass { session } to EVERY operation inside a transaction -- missing session causes that operation to run outside the transaction silently)
(You MUST use .lean() for read-only queries returning API responses -- skipping lean wastes 3x memory on hydration overhead)
(You MUST use 127.0.0.1 instead of localhost in connection strings -- Node.js 18+ prefers IPv6 and localhost causes connection timeouts)
(You MUST NOT use findOneAndUpdate/updateOne and expect pre('save') to fire -- only save() and create() trigger document middleware)
(You MUST NOT use next() callbacks in pre hooks on Mongoose 9 -- use async/await instead; next() was removed in v9)
</critical_requirements>
Auto-detection: Mongoose, mongoose, mongoose.connect, Schema, model, ObjectId, populate, HydratedDocument, InferSchemaType, InferRawDocType, pre('save'), post('save'), lean, mongoose.startSession, withTransaction, discriminator, virtual, Schema.Types.ObjectId, Types.ObjectId
When to use:
Key patterns covered:
When NOT to use:
Detailed Resources:
Core Patterns:
Middleware & Lifecycle:
Relationships & Population:
Transactions & Advanced:
Mongoose provides schema-based modeling for MongoDB. Its value is the application-layer enforcement of structure, validation, middleware, and type safety on top of MongoDB's flexible document model.
Core principles:
model(). This is the single most common Mongoose bug -- hooks added after compilation are silently ignored..lean() returns plain JavaScript objects (3x less memory). Use it for every read-only query. Only skip lean when you need Mongoose document methods.{ session }. One missed session means that operation runs outside the transaction with no error.When to use Mongoose:
When NOT to use Mongoose:
Establish a single connection at application startup. Use environment variables for credentials. Never hardcode connection strings. Use 127.0.0.1 instead of localhost (Node.js 18+ IPv6 preference causes timeouts).
// Named constants for pool/timeout, env var for URI, typed return
const connection = await mongoose.connect(process.env.MONGODB_URI!, {
maxPoolSize: POOL_SIZE_MAX,
serverSelectionTimeoutMS: SERVER_SELECTION_TIMEOUT_MS,
});
See examples/core.md Pattern 1 for production connection setup, event handling, graceful shutdown, and multi-database connections.
Let Mongoose infer types from the schema definition. Only use explicit interfaces when adding methods, statics, or virtuals. Use as const on enum arrays to preserve literal types.
const userSchema = new Schema(
{
email: { type: String, required: true, unique: true, lowercase: true },
role: { type: String, enum: ["admin", "user"] as const, default: "user" },
},
{ timestamps: true },
);
const User = model("User", userSchema); // TypeScript infers types from schema
See examples/core.md Patterns 2-3 for complete schemas with validation, subdocuments, InferSchemaType, and full generic typing with methods/statics/virtuals.
When a model has instance methods, statics, or virtuals, use the full generic parameter set. Define separate interfaces for IDoc, IDocMethods, IDocVirtuals, and IDocStatics. Export HydratedDocument<IDoc, IDocMethods & IDocVirtuals> for consumers.
type UserModel = Model<IUser, {}, IUserMethods, IUserVirtuals> & IUserStatics;
type UserDocument = HydratedDocument<IUser, IUserMethods & IUserVirtuals>;
const userSchema = new Schema<
IUser,
UserModel,
IUserMethods,
{},
IUserVirtuals
>(
{
/* fields */
},
{ toJSON: { virtuals: true } },
);
See examples/core.md Pattern 3 for the complete implementation with all interfaces, generic parameters, methods, virtuals, statics, and middleware ordering.
Key rules: use .lean() for read-only queries (3x memory savings), save() when middleware must fire, { new: true, runValidators: true } on direct updates. Never call .save() on a lean result (plain object, no methods).
const users = await User.find({ isActive: true }).select("name email").lean();
await User.findByIdAndUpdate(
id,
{ $set: { name: "New" } },
{ new: true, runValidators: true },
);
See examples/core.md Pattern 5 for create, read, update, delete, bulk operations, and common mistakes.
Push validation into schema definitions: use required with messages, min/max/minlength/maxlength with messages, match for regex, enum with as const and {VALUE} message template, and custom validate functions. Use named constants for all numeric limits.
name: { type: String, required: [true, "Name is required"], minlength: [MIN_LEN, "Too short"] },
status: { type: String, enum: { values: ["draft", "active"] as const, message: "{VALUE} invalid" } },
See examples/core.md Pattern 2 for complete validation schemas, subdocuments, and array validation.
</patterns><red_flags>
High Priority Issues:
model() call -- hooks are silently ignored, no error thrownPromise.all()) -- MongoDB does not support parallel operations within a single transaction session{ session } on any operation inside a transaction -- that operation runs outside the transaction silentlylocalhost in connection strings on Node.js 18+ -- IPv6 preference causes connection timeouts, use 127.0.0.1.lean() and calling .save() -- lean returns plain objects without Mongoose methodsMedium Priority Issues:
findOneAndUpdate/updateOne and expecting pre('save') to fire -- only save() and create() trigger document middleware.populate() without limit or field selection -- can return thousands of documents per populate call, each is a separate DB round-triprunValidators: true on findOneAndUpdate -- schema validation is skipped by default on direct updatesSchema.Types.ObjectId in TypeScript interfaces -- use Types.ObjectId for interfaces, Schema.Types.ObjectId for schema definitions onlyCommon Mistakes:
{ new: true } on findOneAndUpdate -- returns the old document by default, not the updated onenext() callbacks in pre hooks on Mongoose 9 -- next() was removed in v9, use async/await.lean() on write operations -- lean is for reads onlydoc.isNew in post('save') hooks -- always false after save; capture in pre('save') via this.$locals.wasNewextends Document on interfaces -- deprecated pattern that breaks type inference for lean documents and query filtersGotchas & Edge Cases:
deleteOne/deleteMany on the Model do not trigger document pre('deleteOne') middleware -- they trigger query middleware instead; use doc.deleteOne() for document middlewaretoJSON()/toObject() by default -- set { toJSON: { virtuals: true } } in schema options or they disappear in API responsesinsertMany() does not trigger save middleware -- it triggers insertMany model middleware onlyFilterQuery to QueryFilter -- update TypeScript imports if upgrading{ updatePipeline: true } or they throwcreate() with an array requires array syntax for { session }: Model.create([data], { session }) -- the non-array form Model.create(data, { session }) does not work in transactions</red_flags>
<critical_reminders>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST define all middleware (pre/post hooks) BEFORE calling model() -- hooks registered after model compilation are silently ignored with no error)
(You MUST pass { session } to EVERY operation inside a transaction -- missing session causes that operation to run outside the transaction silently)
(You MUST use .lean() for read-only queries returning API responses -- skipping lean wastes 3x memory on hydration overhead)
(You MUST use 127.0.0.1 instead of localhost in connection strings -- Node.js 18+ prefers IPv6 and localhost causes connection timeouts)
(You MUST NOT use findOneAndUpdate/updateOne and expect pre('save') to fire -- only save() and create() trigger document middleware)
(You MUST NOT use next() callbacks in pre hooks on Mongoose 9 -- use async/await instead; next() was removed in v9)
Failure to follow these rules will cause silent middleware bypass, transaction isolation failures, or connection timeouts.
</critical_reminders>
development
Material Design component library for Vue 3
development
VitePress 1.x — Vue-powered static site generator for documentation sites, built on Vite
tools
Docusaurus 3.x documentation framework — site configuration, docs/blog plugins, sidebars, versioning, MDX, swizzling, and deployment
development
TanStack Form patterns - useForm, form.Field, validators, arrays, linked fields, createFormHook, type safety