CRUD advanced.

Array updates

// Add unique
db.coll.updateOne({ _id }, { $addToSet: { tags: "x" } })
db.coll.updateOne({ _id }, { $addToSet: { tags: { $each: ["a", "b"] } } })

// Push
db.coll.updateOne({ _id }, { $push: { items: { ts: new Date() } } })
db.coll.updateOne({ _id }, { $push: { items: { $each: [...], $slice: -100, $sort: { ts: -1 } } } })

// Remove
db.coll.updateOne({ _id }, { $pull: { tags: "x" } })
db.coll.updateOne({ _id }, { $pull: { items: { ts: { $lt: cutoff } } } })
db.coll.updateOne({ _id }, { $pullAll: { tags: ["a", "b"] } })
db.coll.updateOne({ _id }, { $pop: { items: -1 } })   // -1=first, 1=last

Update array element

// By position
db.coll.updateOne({ _id }, { $set: { "items.0.done": true } })

// By match ($)
db.coll.updateOne({ _id, "items.id": "abc" }, { $set: { "items.$.done": true } })

// All matching ($[])
db.coll.updateOne({ _id }, { $set: { "items.$[].done": true } })

// Filtered ($[<id>])
db.coll.updateOne(
    { _id },
    { $set: { "items.$[item].done": true } },
    { arrayFilters: [{ "item.priority": "high" }] }
)

findOneAndUpdate (atomic)

const doc = db.counters.findOneAndUpdate(
    { _id: "users" },
    { $inc: { value: 1 } },
    { upsert: true, returnDocument: "after" }
)

Returns the doc; atomic increment.

findOneAndDelete

const job = db.jobs.findOneAndDelete(
    { status: "ready" },
    { sort: { priority: -1 } }
)

For job queues.

Upsert

db.users.updateOne(
    { email: "..." },
    { $set: { name: "Alice", updated: new Date() }, $setOnInsert: { created: new Date() } },
    { upsert: true }
)

Bulk write

db.coll.bulkWrite([
    { insertOne: { document: {...} } },
    { updateOne: { filter: {...}, update: { $set: {...} } } },
    { updateMany: { filter: {...}, update: {...} } },
    { deleteOne: { filter: {...} } },
    { replaceOne: { filter: {...}, replacement: {...} } },
], { ordered: false })

ordered: false = parallel; faster but errors don’t stop.

Cursor

const cursor = db.coll.find({}).batchSize(1000)
while (cursor.hasNext()) {
    const doc = cursor.next()
    // process
}

Or in driver: .toArray() for small, iterate for large.

Pagination

// Offset (slow for big skip)
db.coll.find().skip(1000).limit(20)

// Cursor-based (fast)
db.coll.find({ _id: { $gt: last_id } }).sort({ _id: 1 }).limit(20)

Prefer cursor.

Transactions

const session = db.getMongo().startSession()
session.startTransaction()
try {
    db.accounts.updateOne({ _id: a }, { $inc: { balance: -100 } }, { session })
    db.accounts.updateOne({ _id: b }, { $inc: { balance: 100 } }, { session })
    session.commitTransaction()
} catch (e) {
    session.abortTransaction()
} finally {
    session.endSession()
}

Replica set / sharded cluster required.

Read / write concern

db.coll.insertOne(
    { ... },
    { writeConcern: { w: "majority", j: true } }   // majority + journal
)
db.coll.find({}, {}).readConcern("snapshot")

Change streams

const stream = db.coll.watch([
    { $match: { operationType: "insert" } }
])

for (const change of stream) {
    process(change)
}

CDC pattern. Requires replica set.

Projection

// Include
db.users.find({}, { name: 1, email: 1, _id: 0 })

// Exclude
db.users.find({}, { password: 0 })

// Array slice
db.posts.find({}, { comments: { $slice: 5 } })
db.posts.find({}, { comments: { $slice: [10, 5] } })   // skip 10, take 5

Common mistakes

  • _id excluded but you still want it (include explicitly).
  • Forgetting arrayFilters syntax.
  • Bulk write without ordered: false → all-or-nothing slow.
  • Transaction in standalone (requires RS).
  • Big skip pagination (slow).

Read this next

If you want my MongoDB recipes, they’re at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .