MongoDBNode.js Native Driver

Native Node.js Driver

The official mongodb npm package is the driver every ODM (including Mongoose) is built on top of. Using it directly gives you the thinnest possible layer between your code and the wire protocol — full control, minimal overhead, and no imposed schema.

MongoClient and Connection Pooling

JS
import { MongoClient } from 'mongodb'

const client = new MongoClient('mongodb://localhost:27017', {
  maxPoolSize: 20,       // max concurrent connections in the pool
  minPoolSize: 5,
  serverSelectionTimeoutMS: 5000
})

await client.connect()
const db = client.db('shop')
const users = db.collection('users')

// One MongoClient instance per process — it manages a connection pool
// internally, so create it once and reuse it across requests.
Tip
Never create a new MongoClient per request. Instantiate it once at application start and share the pooled client — that pool is what makes concurrent requests fast.
CRUD Methods

JS
// Create
await users.insertOne({ email: 'alice@example.com', name: 'Alice' })
await users.insertMany([{ name: 'Bob' }, { name: 'Carol' }])

// Read
const alice = await users.findOne({ email: 'alice@example.com' })
const admins = await users.find({ role: 'admin' }).toArray()

// Update
await users.updateOne({ _id: alice._id }, { $set: { name: 'Alice S.' } })
await users.updateMany({ role: 'user' }, { $set: { active: true } })

// Delete
await users.deleteOne({ _id: alice._id })
await users.deleteMany({ active: false })
Cursors with Async Iteration

find() returns a cursor, not an array — use async iteration to stream results without loading everything into memory at once.

JS
const cursor = users.find({ role: 'user' }).project({ email: 1, name: 1 })

for await (const doc of cursor) {
  console.log(doc.email)
}

// Or materialize when the result set is known to be small
const all = await users.find({ role: 'admin' }).toArray()
Transactions

Transactions require a replica set or sharded cluster (not a standalone mongod). Use a session and wrap the operations in withTransaction, which handles retries on transient errors automatically.

JS
const session = client.startSession()

try {
  await session.withTransaction(async () => {
    await db.collection('accounts').updateOne(
      { _id: fromId }, { $inc: { balance: -amount } }, { session }
    )
    await db.collection('accounts').updateOne(
      { _id: toId }, { $inc: { balance: amount } }, { session }
    )
  }, {
    readConcern: { level: 'snapshot' },
    writeConcern: { w: 'majority' }
  })
} finally {
  await session.endSession()
}
Error Handling

JS
import { MongoServerError } from 'mongodb'

try {
  await users.insertOne({ email: 'alice@example.com' })
} catch (err) {
  if (err instanceof MongoServerError && err.code === 11000) {
    console.error('Duplicate email')
  } else {
    throw err
  }
}
Connection String Options

Option

Purpose

maxPoolSize

Maximum connections held open per client (default 100)

minPoolSize

Connections kept warm even when idle

retryWrites=true

Automatically retries a write once on a transient network/failover error

w=majority

Default write concern — wait for acknowledgment from a majority of replica set members

readPreference

primary, primaryPreferred, secondary, secondaryPreferred, nearest

authSource

Database the credentials are validated against (often admin)

tls=true

Enable TLS for the connection (required by Atlas)

Bash
mongodb+srv://appUser:pass@cluster0.mongodb.net/shop?retryWrites=true&w=majority&authSource=admin
Mongoose vs Native Driver
  • Native driver: minimal overhead, full control, no imposed schema — best for high-throughput services or libraries.

  • Mongoose: schemas, validation, middleware, and populate() at the cost of a small hydration/casting overhead.

  • Many teams use Mongoose for typical CRUD app code and drop to the native driver (accessible via mongoose.connection.db) for performance-critical aggregation pipelines.

Warning
Always close the client on graceful shutdown (await client.close()) so connections are released cleanly — leaving them open can exhaust the server's connection limit under repeated redeploys.
Note
Every official driver (Node.js, Python, Java, Go, C#, Rust...) speaks the same MongoDB wire protocol, so the concepts here — pooling, cursors, sessions/transactions — carry directly over to other languages, including the Python driver covered next.