Bulk Write Operations
When you need to apply many different writes at once — some inserts, some updates, some deletes — sending each as its own round trip is slow. bulkWrite() batches a mix of operations into a single call to the server, cutting network overhead dramatically.
Basic bulkWrite()
db.products.bulkWrite([
{ insertOne: { document: { sku: "A1", price: 9.99 } } },
{ updateOne: {
filter: { sku: "B2" },
update: { $set: { price: 14.99 } }
} },
{ updateMany: {
filter: { category: "clearance" },
update: { $mul: { price: 0.8 } }
} },
{ deleteOne: { filter: { sku: "C3" } } },
{ replaceOne: {
filter: { sku: "D4" },
replacement: { sku: "D4", price: 19.99, name: "New Name" }
} }
])The Five Operation Types
Operation | Shape |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Ordered vs Unordered
By default, bulkWrite() is ordered: operations run in the array's order, and the first failure stops all remaining operations. Pass { ordered: false } to run every operation independently — MongoDB attempts them all (in undefined order, in parallel where possible) and reports every failure together.
// Ordered (default) — stops at the first error
db.products.bulkWrite(operations)
// Unordered — every operation is attempted regardless of earlier failures
db.products.bulkWrite(operations, { ordered: false })Ordered | Unordered | |
|---|---|---|
On error | Stops immediately | Continues with remaining ops |
Execution | Strictly sequential | May run in parallel internally |
Performance | Slightly slower (sequential) | Faster for large independent batches |
Use when | Later ops depend on earlier ones succeeding | Operations are independent of each other |
ordered: false for large, independent batch jobs (bulk imports, sync jobs) — you get better throughput and a complete picture of every failure instead of stopping at the first.Write Results
const result = await db.products.bulkWrite(operations) console.log(result)
{
insertedCount: 1,
matchedCount: 12,
modifiedCount: 12,
deletedCount: 1,
upsertedCount: 0,
upsertedIds: {},
insertedIds: { '0': ObjectId("...") }
}Handling Partial Failures — BulkWriteError
With ordered: false, some operations can fail (e.g. a duplicate key) while others succeed. The driver throws a BulkWriteError that contains both the successes so far and the details of every failure.
try {
await db.products.bulkWrite(operations, { ordered: false })
} catch (err) {
if (err.name === "MongoBulkWriteError") {
console.log("Succeeded before error:", err.result.insertedCount)
console.log("Write errors:", err.writeErrors.map(e => ({
index: e.index,
code: e.code,
message: e.errmsg
})))
} else {
throw err
}
}ordered: false, a failed operation does NOT roll back the ones that already succeeded — bulkWrite is not a transaction. If you need all-or-nothing semantics across multiple writes, use a session-based transaction instead (see the Node.js Driver page).Performance vs Individual Operations
Each individual
insertOne/updateOnecall is a separate round trip to the server — network latency dominates for large batches.bulkWrite()sends the whole batch (up to the 100,000-operation / 48MB command size limit) in far fewer round trips.For pure inserts with no per-document logic,
insertMany()is simpler and just as fast as an all-insertOne bulkWrite.bulkWrite()earns its keep when the batch is a MIX of operation types, or needs different filters/updates per document.
Worked Example: Syncing a Price List
// Given an array of { sku, price } from an external feed, upsert each
const priceUpdates = [
{ sku: "A1", price: 12.5 },
{ sku: "B2", price: 8.0 },
{ sku: "NEW-SKU", price: 20.0 }
]
const ops = priceUpdates.map(({ sku, price }) => ({
updateOne: {
filter: { sku },
update: { $set: { price } },
upsert: true // insert if the SKU doesn't exist yet
}
}))
const result = await db.products.bulkWrite(ops, { ordered: false })
console.log(`Matched: ${result.matchedCount}, Upserted: ${result.upsertedCount}`)Summary
bulkWrite() batches insertOne/updateOne/updateMany/deleteOne/deleteMany/replaceOne into one server round trip.
Ordered (default): stops on first error, guarantees sequence. Unordered: attempts everything, better throughput.
Write results report counts per operation type; a partial failure with unordered writes throws BulkWriteError with details for each failed op.
bulkWrite is not atomic across operations — use transactions when you need all-or-nothing.