Skip to content
Node.js7 min read

MongoDB and Mongoose Best Practices in Production

Lean queries, sensible indexing, schema discipline and connection handling — the Mongoose habits that keep MongoDB fast and predictable in production.

MongoDB earns its reputation for developer speed, and Mongoose adds structure that raw drivers lack. But the defaults that make prototypes quick can make production slow, and most of the Mongo performance work we do for clients comes down to the same handful of corrections. Here they are, in the order they usually pay off.

Use lean() for every read you do not mutate

By default Mongoose hydrates every result into a full document instance with change tracking, getters and methods attached. For a list endpoint returning two hundred records, that is two hundred class instances built purely to be serialised to JSON and thrown away. Adding lean() returns plain objects and routinely cuts read-path CPU and memory dramatically.

js
const orders = await Order.find({ userId })
  .select('status total createdAt')
  .sort({ createdAt: -1 })
  .limit(50)
  .lean();

The select() call matters too: projecting only needed fields shrinks network transfer and lets covered queries serve results straight from the index. Reserve full documents for code paths that actually call save().

Indexes: deliberate, not decorative

Every query shape your application runs should be backed by an index, and compound indexes should follow the equality-sort-range rule: fields matched by equality first, then the sort field, then range conditions. The query above wants an index on { userId: 1, createdAt: -1 }. Verify with explain() rather than intuition, and watch for COLLSCAN in the plan.

Just as important: turn autoIndex off in production. Letting the app create indexes at boot means a deploy can silently trigger an index build on a large collection at peak traffic. Manage index changes as deliberate migrations instead.

Schema discipline in a schemaless database

  • Enable strict mode (the default) and consider strictQuery so typos in filters fail loudly instead of matching nothing.
  • Prefer referencing over embedding when the embedded array grows without bound — unbounded arrays are the classic Mongo scaling trap.
  • Set runValidators: true on updates; validators only run on save() by default, which surprises nearly everyone.
  • Give every schema explicit timestamps rather than relying on ObjectId creation time.

The unbounded array point deserves a sentence more. A conversation document embedding its messages works beautifully until one conversation has forty thousand messages and a 16 MB document limit problem. Model the many side as its own collection whenever the cardinality has no natural ceiling.

Connections and failure behaviour

Mongoose maintains a connection pool per process; the default pool size of 100 is usually far too generous for containerised deployments and can exhaust Atlas connection limits as you scale replicas. Size maxPoolSize to what each instance genuinely needs. Set sensible serverSelectionTimeoutMS so a dead cluster fails requests in seconds rather than queueing them for minutes, and handle the initial connection failure explicitly — an API that boots without its database should crash and let the orchestrator retry, not limp along returning 500s.

Two production capabilities are worth wiring before you need them. Slow-query visibility: enable Mongoose debug output in development, and in production watch the database profiler or Atlas performance advisor rather than inferring from API latency. And transactions: they require a replica set, which Atlas provides on every tier, and they are the right tool the moment one business operation writes two documents that must agree — order plus stock decrement being the classic pair. Sessions flow through Mongoose cleanly; the discipline is keeping transactions short and retrying on transient errors.

None of this is exotic. Lean reads, deliberate indexes, bounded documents and honest connection settings cover the large majority of Mongo incidents we are brought in to resolve — and all four are cheapest to adopt before the data grows.

Start your project

Have an idea? Let's ship it together.

Tell us what you're building — we'll reply within one business day with an honest take and a clear next step.