Learn-skills.dev api-database-mongodb
MongoDB with Mongoose ODM - schemas, models, queries, aggregation, indexes, TypeScript typing, connection management
git clone https://github.com/NeverSight/learn-skills.dev
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/agents-inc/skills/api-database-mongodb" ~/.claude/skills/neversight-learn-skills-dev-api-database-mongodb && rm -rf "$T"
data/skills-md/agents-inc/skills/api-database-mongodb/SKILL.mdMongoDB / Mongoose Patterns
Quick Guide: Use Mongoose as the ODM for MongoDB. Define schemas with automatic TypeScript inference, use
for read-only queries, prefer embedding over referencing for co-accessed data, placelean()early in aggregation pipelines, and always define indexes to match your query patterns.$match
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
, named constants)import type
(You MUST define Mongoose middleware (pre/post hooks) BEFORE calling
-- hooks registered after model compilation are silently ignored)model()
(You MUST pass
to EVERY operation inside a transaction -- missing session causes operations to run outside the transaction){ session }
(You MUST use
for read-only queries that send results directly to API responses -- skipping lean wastes 3x memory on hydration overhead).lean()
(You MUST use
instead of 127.0.0.1
in connection strings -- Node.js 18+ prefers IPv6 and localhost
can cause connection timeouts)localhost
(You MUST NOT use
/ findOneAndUpdate
and expect updateOne
middleware to fire -- only save
and save()
trigger document middleware)create()
</critical_requirements>
Auto-detection: MongoDB, Mongoose, mongoose.connect, Schema, model, ObjectId, populate, aggregate, $match, $group, $lookup, lean, HydratedDocument, InferSchemaType, MongoClient, Atlas
When to use:
- Defining MongoDB schemas and models with Mongoose
- Building CRUD operations and complex queries
- Designing aggregation pipelines for analytics and reporting
- Managing indexes for query performance
- Connecting to MongoDB Atlas or local instances
- Modeling document relationships (embedding vs referencing)
Key patterns covered:
- Connection setup (Atlas URI, pooling, error handling)
- Schema definition (types, validation, defaults, enums)
- Models with TypeScript (automatic inference, methods, statics, virtuals)
- CRUD operations (create, find, update, delete, lean)
- Query building (filters, projection, sort, limit, populate)
When NOT to use:
- Highly relational data with complex joins and foreign key constraints (use a relational database)
- Strong ACID guarantees across many collections as a primary pattern (use a relational database)
- Simple key-value storage (use a dedicated key-value store)
- Fixed schemas where relational constraints are critical
- Time-series data at scale (use a dedicated time-series database)
Detailed Resources:
- For decision frameworks and anti-patterns, see reference.md
Core Patterns:
- examples/core.md - Connection, schema definition, model creation, TypeScript typing
Query Patterns:
- examples/queries.md - Complex queries, populate, lean, cursor, pagination
Aggregation:
- examples/aggregation.md - Aggregation pipeline, $match, $group, $lookup, $project
Advanced Patterns:
- examples/patterns.md - Schema design (embedding vs referencing), transactions, middleware hooks, virtuals
Indexing:
- examples/indexes.md - Index types, compound indexes, text search, geospatial, TTL, performance
<philosophy>
Philosophy
MongoDB is a document database. Mongoose provides schema-based modeling on top of it. The core principle: data that is accessed together should be stored together.
Core principles:
- Schema-first design -- Define schemas before models. Schemas enforce structure, validation, and defaults at the application layer.
- Embed by default -- Co-accessed data belongs in the same document. Only reference when data is shared across many documents, grows unbounded, or is frequently updated independently.
- Lean for reads -- Use
for read-only queries. It returns plain objects (3x less memory) instead of full Mongoose documents..lean() - Index your queries -- Every query pattern needs a supporting index. Compound indexes follow the Equality-Sort-Range (ESR) rule.
- Aggregation over application logic -- Push data transformation to the database with aggregation pipelines instead of processing in application code.
- TypeScript inference -- Let Mongoose infer types from schema definitions. Avoid manually duplicating interfaces unless you need methods/statics/virtuals.
When to use MongoDB / Mongoose:
- Document-oriented data (user profiles, product catalogs, content)
- Flexible schemas that evolve over time
- Hierarchical or nested data structures
- High read throughput with embedding
- Geospatial queries and full-text search
<patterns>
Core Patterns
Pattern 1: Connection Setup
Establish a single connection at startup with named constants for pool/timeout config and environment variables for credentials. See examples/core.md for full examples including connection events and graceful shutdown.
const connection = await mongoose.connect(process.env.MONGODB_URI!, { maxPoolSize: POOL_SIZE_MAX, minPoolSize: POOL_SIZE_MIN, serverSelectionTimeoutMS: SERVER_SELECTION_TIMEOUT_MS, socketTimeoutMS: SOCKET_TIMEOUT_MS, retryWrites: true, retryReads: true, });
Pattern 2: Schema Definition with TypeScript
Let Mongoose infer types from the schema definition. Use explicit interfaces only when adding methods, statics, or virtuals. See examples/core.md for full typing examples with
HydratedDocument, InferSchemaType, and generic parameters.
// Preferred: automatic type inference const userSchema = new Schema( { name: { type: String, required: true, trim: true }, email: { type: String, required: true, unique: true, lowercase: true }, role: { type: String, enum: ["admin", "user", "moderator"] as const, default: "user", }, }, { timestamps: true }, ); const User = model("User", userSchema);
// For methods/statics/virtuals: explicit interfaces with Schema generics const userSchema = new Schema<IUser, UserModel, IUserMethods, {}, IUserVirtuals>({ ... });
Pattern 3: CRUD Operations
Use
.lean() for read-only queries, save() when middleware must fire, findByIdAndUpdate with { runValidators: true } for direct updates. See examples/core.md for full CRUD examples.
const user = await User.findById(id).lean(); // read-only, 3x less memory await User.insertMany(users, { ordered: false }); // bulk insert await User.findByIdAndUpdate(id, update, { new: true, runValidators: true }); // direct update
Pattern 4: Query Building
Use comparison/logical operators for filters,
.populate() with field selection and limits. See examples/queries.md for dynamic query builders, cursor-based pagination, and populate patterns.
const post = await Post.findById(id) .populate("author", "name email") .populate({ path: "comments", options: { sort: { createdAt: -1 }, limit: 10 }, }) .lean();
Pattern 5: Schema Validation
Add custom error messages, regex validation, and array-level validators. See examples/core.md for complete validation examples.
</patterns>price: { type: Number, required: true, min: [0, "Price cannot be negative"], validate: { validator: (v: number) => Number.isFinite(v), message: "Price must be finite" }, }, sku: { type: String, required: true, unique: true, match: [/^[A-Z]{2}-\d{6}$/, "SKU must match format XX-000000"], },
<red_flags>
RED FLAGS
High Priority Issues:
- Mutating a document fetched with
and expecting.lean()
to work -- lean returns plain objects without Mongoose methods.save() - Registering middleware after
call -- hooks are silently ignoredmodel() - Running operations in parallel inside a transaction (
) -- MongoDB does not support parallel operations within a single transactionPromise.all() - Using
in connection strings on Node.js 18+ -- IPv6 preference causes connection timeouts, uselocalhost127.0.0.1 - Missing
on any operation inside a transaction -- that operation runs outside the transaction{ session }
Medium Priority Issues:
- Using
/findOneAndUpdate
and expectingupdateOne
hooks to fire -- onlypre('save')
andsave()
trigger document middlewarecreate() - Unbounded
without.populate()
or field selection -- can return thousands of documents per querylimit - Not calling
onrunValidators: true
-- schema validation is skipped by default on updatesfindOneAndUpdate - Creating indexes in production code instead of migration scripts -- index builds lock the collection
- Using
or JavaScript expressions in queries -- disables indexes and enables injection$where
Common Mistakes:
- Forgetting
on{ new: true }
-- returns the old document by defaultfindOneAndUpdate - Using
in TypeScript interfaces instead ofSchema.Types.ObjectId
--Types.ObjectId
is for schema definitions,Schema.Types.ObjectId
is for interfacesTypes.ObjectId - Not handling duplicate key errors (code 11000) from unique indexes
- Calling
on write operations -- lean is for reads only.lean() - Checking
indoc.isNew
hooks -- alwayspost('save')
after save, usefalse
set in athis.$locals.wasNew
hookpre('save')
Gotchas & Edge Cases:
- MongoDB has a 16 MB document size limit -- deeply embedded arrays can hit this
- Mongoose buffers operations before connection is established -- queries queue silently if connection fails
/deleteOne
do not triggerdeleteMany
middleware -- usepre('remove')
or documentfindOneAndDelete
if you need middleware.deleteOne()- Virtual properties are excluded from
/toJSON()
by default -- settoObject()
in schema options{ toJSON: { virtuals: true } }
was completely removed in Mongoose 7+ -- useremove()
ordeleteOne()
insteaddeleteMany()- Mongoose 9 dropped callback-based
in pre hooks -- use async/await insteadnext() - Mongoose 9 renamed
toFilterQuery
-- update TypeScript imports if upgradingQueryFilter - Mongoose 9 requires
for pipeline-style updates -- they throw by defaultupdatePipeline: true - Mongoose 9 removed the
index option -- MongoDB 4.2+ builds all indexes in the background by defaultbackground
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
, named constants)import type
(You MUST define Mongoose middleware (pre/post hooks) BEFORE calling
-- hooks registered after model compilation are silently ignored)model()
(You MUST pass
to EVERY operation inside a transaction -- missing session causes operations to run outside the transaction){ session }
(You MUST use
for read-only queries that send results directly to API responses -- skipping lean wastes 3x memory on hydration overhead).lean()
(You MUST use
instead of 127.0.0.1
in connection strings -- Node.js 18+ prefers IPv6 and localhost
can cause connection timeouts)localhost
(You MUST NOT use
/ findOneAndUpdate
and expect updateOne
middleware to fire -- only save
and save()
trigger document middleware)create()
Failure to follow these rules will cause silent data corruption, middleware bypass, or transaction isolation failures.
</critical_reminders>