insertOne, insertMany and bulk writes
Create documents safely: generated ids, ordered versus unordered batches, duplicate key errors and the write result.
- Insert single and multiple documents
- Read the acknowledgement result
- Choose ordered or unordered inserts
- Handle E11000 duplicate key errors
conceptThe concept
insertOne(doc) returns { acknowledged, insertedId }. insertMany([...], { ordered }) sends a batch: with ordered: true (default) it stops at the first error; with ordered: false it attempts every document and reports all errors at the end, which is faster and the usual choice for bulk loads.
bulkWrite() mixes inserts, updates, replaces and deletes in one round trip. Drivers split large batches automatically (up to 100,000 operations or 48 MB per message).
A duplicate value in a unique index (including _id) fails that document with error code 11000 (E11000 duplicate key error).
whyWhy it matters
Batching matters: inserting 100,000 documents one by one costs 100,000 network round trips and journal commits; insertMany with unordered batches can be orders of magnitude faster. Treating 11000 as an expected outcome is how you build idempotent ingestion.
Use caseReal-world scenario
A nightly import reads 2 million product records from a supplier feed. It uses insertMany(batch, { ordered: false }) with 1,000 documents per batch, collects any duplicate-key errors into a report, and moves on rather than aborting the whole job.
Try itExamples
Run and edit these in the interactive lesson.
db.lab_people.drop()
db.lab_people.insertOne({ name: "Asha", email: "asha@example.com", skills: ["mongodb"] })
db.lab_people.insertMany([
{ name: "Ben", email: "ben@example.com" },
{ _id: "u-carla", name: "Carla", email: "carla@example.com" }
])
db.lab_people.find()db.lab_people.createIndex({ email: 1 }, { unique: true })
db.lab_people.insertMany([
{ name: "Dev", email: "dev@example.com" },
{ name: "Ben again", email: "ben@example.com" },
{ name: "Eva", email: "eva@example.com" }
], { ordered: false })ordered: true on new emails to see it stop at the first error.db.lab_people.deleteMany({ email: "femi@example.com" })
db.lab_people.bulkWrite([
{ insertOne: { document: { name: "Femi", email: "femi@example.com" } } },
{ updateOne: { filter: { email: "asha@example.com" }, update: { $addToSet: { skills: "aggregation" } } } },
{ deleteOne: { filter: { email: "carla@example.com" } } }
])
db.lab_people.countDocuments()internalsUnder the hood
The driver adds _id before sending if you did not. On the server each document is a separate storage-engine write plus an oplog entry; an insertMany is not a transaction, so a failure part-way leaves earlier documents inserted. Wrap it in a transaction only if you truly need all-or-nothing behaviour.
Avoid / preferDo this, not that
for (const d of docs) db.events.insertOne(d)
db.events.insertMany(docs, { ordered: false })One round trip per document is slow. insertMany batches them, and ordered: false keeps going past individual failures.
if (!db.users.findOne({ email: "a@x.com" }))
db.users.insertOne({ email: "a@x.com" })db.users.createIndex({ email: 1 }, { unique: true })
db.users.insertOne({ email: "a@x.com" }) // E11000 if takenCheck-then-insert races: two requests can both see no user and both insert. Only a unique index makes the rule hold under concurrency.
PitfallsCommon mistakes
- Generating your own string ids without a unique index on the natural key, then importing duplicates.
- Assuming
insertManyis atomic. It is not; use idempotent keys or a transaction. - Retrying a failed batch from the beginning with ordered inserts and failing on the first duplicate every time. Use unordered or upserts.
LimitsWhen not to use it
insertMany and bulkWrite are not transactions: if the batch fails halfway, the earlier writes stay. Use a transaction when all-or-nothing matters. For loading large files, mongoimport or mongorestore with parallel workers beats a hand-written loop.
SecuritySecurity notes
Never insert a request body as it arrives: db.users.insertOne(req.body) lets a client set role: "admin", its own _id or any other field. Copy only the fields you expect, and back that up with a $jsonSchema validator.
ChallengeTry it yourself
In a scratch collection tickets_lab, insert three documents with ordered: false where the second duplicates the first _id. Catch the error, print its code, and show how many documents were stored.
Show one solution
db.tickets_lab.drop()
try {
db.tickets_lab.insertMany([{ _id: 1, t: "a" }, { _id: 1, t: "dup" }, { _id: 2, t: "b" }], { ordered: false })
} catch (e) { print("error code", e.code) }
db.tickets_lab.countDocuments()InterviewQuestions you should be able to answer
How do you make a bulk import idempotent?
Give each record a deterministic key (natural key or _id derived from the source), enforce it with a unique index, and either insert unordered and ignore 11000 errors, or use bulkWrite with updateOne(..., { upsert: true }) / replaceOne so re-running converges to the same state.
QuizCheck your understanding
4 questions with instant feedback in the interactive lesson; a wrong answer goes into your review deck.