MongoDB Mastery
MongoDB Mastery lessons / Module 4: Inserting Documents

insertOne, insertMany and bulk writes

Level 1: Beginner15 min

Create documents safely: generated ids, ordered versus unordered batches, duplicate key errors and the write result.

This lesson is interactive. Run every example against an in-browser MongoDB, see explain() plans drawn as trees, and take the quiz.
Open the interactive lesson
You will learn to
  • 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.

Insert one, insert many
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()
Duplicate keys: ordered vs unordered
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 })
Unordered: Dev and Eva are inserted even though Ben fails. Re-run with ordered: true on new emails to see it stop at the first error.
Bulk write
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

Avoid
for (const d of docs) db.events.insertOne(d)
Prefer
db.events.insertMany(docs, { ordered: false })

One round trip per document is slow. insertMany batches them, and ordered: false keeps going past individual failures.

Avoid
if (!db.users.findOne({ email: "a@x.com" }))
  db.users.insertOne({ email: "a@x.com" })
Prefer
db.users.createIndex({ email: 1 }, { unique: true })
db.users.insertOne({ email: "a@x.com" })   // E11000 if taken

Check-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 insertMany is 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
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.