Mongoose 9 adds models and middleware over the MongoDB driver
Mongoose 9 gives MongoDB documents an application model with declared paths, casting, defaults, validators, indexes, methods, statics, and middleware. Population supplies references that resemble joins, while embedded schemas handle nested documents. This layer can make a large Node.js codebase easier to reason about because read and write rules live near the model. It can also surprise developers who expect every call to behave exactly like the underlying driver.
Direct collection access remains available through each model, but the README says it bypasses Mongoose hooks and validation. Even that escape hatch keeps command buffering. The choice is therefore broader than query syntax: adopting Mongoose means accepting its model lifecycle and connection behavior. Small services with 2 or 3 straightforward collections may be clearer on the official driver. Applications with repeated casting, document methods, and save hooks have a stronger case for the added layer.
What happened when we ran it
Our sandbox installed 616 npm packages in 44 seconds and occupied 180 MB. The repository had no build script or target, so the build step was skipped. npm audit found 0 known vulnerabilities. These results came from commit 86292f0 in a fresh unprivileged Debian container with 3 CPUs and 8 GB of RAM. Installation was uneventful; the test stage was not.
The test command reached the 900-second limit and was terminated without a final pass or fail summary. The log tail showed green checks for handles toObject(), discriminator child schemas, several deep population cases, and a 4-level population case. It then printed numbered entries 77 through 79 for out-of-order discriminators, a dynref bug, and an array toObject() case. The log does not prove why the suite stopped, so we do not assign a cause or invent a completed count.
The checkout measured 7.8 MB, with 708 files and roughly 190,697 lines of source. We found 9 CI workflow files, no Dockerfile, and a tests directory. A 15-minute timeout is meaningful friction for contributors even when individual checks continue to pass near the end. Open issue 16284 separately asks maintainers to investigate flaky CI tests, but that issue does not establish the reason for our timeout.
Node.js 20.19 and a MongoDB server are required
Mongoose 9 requires Node.js 20.19.0 or newer. The README's first setup also requires MongoDB, then connects with a mongodb:// URI. A local trial can use the default port 27017, while a managed cluster normally adds credentials, TLS, and network access rules. No application build command is required for the package itself. You still need a database lifecycle for development, CI, staging, backups, and production.
Deno support exists, but the README labels it alpha. Its example loads Mongoose through createRequire() and grants network, read, system, and environment permissions. That is useful for experiments and too soft a promise for a team standardizing a production Deno stack. Bun, pnpm, Yarn, and npm installation commands are documented, while the runtime support statement remains centered on Node.js.
A 30-second server-selection default can delay connection failures
Mongoose buffers model operations until it connects unless bufferCommands is disabled. The connection guide warns that buffering can hide a missing connection because an operation appears to hang. The MongoDB driver also uses a 30-second serverSelectionTimeoutMS default before an unavailable server causes an error. That tolerance helps replica-set failover, but it can be painful in short serverless requests or health checks unless the timeout policy is chosen deliberately.
Initial connection failure rejects the connect() promise and does not trigger automatic retry. A later disconnect follows a different path: Mongoose attempts to reconnect, while applications should watch connection events. Pool size defaults and index behavior also matter under load. The docs say autoIndex is convenient in development and unsuitable for some large production deployments because building indexes can hurt performance. Treat connection, pool, and index settings as deployment configuration, not library trivia.
Version 9 changes middleware, updates, and identifier behavior
The v9 migration guide removes the next() parameter from pre middleware and expects async functions or promises. Update pipelines now throw unless updatePipeline: true is set because Mongoose does not cast pipeline updates. Numeric values no longer count as valid ObjectIds, several callback forms are gone, and UUID handling returns BSON UUID objects. An older codebase needs focused tests around middleware and serialization before changing the major version.
Validation has its own sharp edges. The unique schema option creates a MongoDB unique index; it is not a Mongoose validator. Validators for update methods are off by default and must be enabled with runValidators, after which they still run only on updated paths and supported operators. These rules are documented well, yet they defeat the common assumption that declaring a schema guarantees every database write follows all document validators.
Release 9.9.5 is current, while one driver-resolution report needs checking
Mongoose 9.9.5 was released September 4, 2026, the same date as the repository's last push. GitHub listed 27,472 stars and 170 combined issues and pull requests. The release fixed nested expression casting, inclusive projection paths, custom cast messages, and a search-index return type. Recent code and issue activity show active maintenance; the combined open count is not a bug count.
Issue 16499, opened September 7, reports a pnpm setup where Mongoose's ~7.5 driver range resolved to MongoDB driver 7.6.0 and a mongodb-memory-server handshake failed. The reproduction text names Mongoose 9.9.4, while the issue title also names 9.9.5, so buyers should reproduce their own lockfile rather than generalize the report. Mongoose remains a good fit for model-heavy MongoDB applications, but our 900-second unfinished suite keeps it from being an automatic default.

