insertOne: One Document at a Time
insertOne() takes a single object and writes it into a collection. If the collection does not exist yet, it is created. If you did not supply an _id, MongoDB generates an ObjectId. The method returns a small result object rather than the document you inserted, and it is worth reading that result carefully the first few times.
acknowledged: true means the server confirmed the write, not merely that the message left your machine. insertedId gives you the _id of the new document, which is usually the thing you need next — to redirect the user to their new order, or to store the id inside another document. In application code you almost always capture it: const { insertedId } = await db.collection('orders').insertOne(order).
Notice what MongoDB does not do here. It does not check that email is present, that age is a number, or that this email address is already in use. Unless you have added an index or a validator, an insert is accepted as-is. That freedom is the point of the lesson on validation later.
use shopDB
db.users.insertOne({
name: "Ananya Sharma",
email: "ananya@example.com",
age: 22,
city: "Pune",
joined: new Date()
})
// {
// acknowledged: true,
// insertedId: ObjectId("66ab12cd34ef56789012ab34")
// }
// Supplying your own _id when a natural key exists
db.students.insertOne({
_id: "ROLL-2026-118",
name: "Rahul Verma",
branch: "Computer Science"
}) Getting the Types Right at Insert Time
The type you store is the type you get back, and fixing a wrong type across a million documents later is painful. Two mistakes cause most of the damage, and both look harmless when you make them.
The first is storing a date as a string. "2026-01-15" is text, and text compares character by character. A query for orders after 1 January will behave itself for a while and then quietly break the day someone writes "15/01/2026" instead. Store dates with new Date() or ISODate("...") so MongoDB stores a true date and can compare, sort and group by it.
The second is money. In mongosh every plain number you type is stored as a 64-bit floating point value, the same as in JavaScript, and floating point cannot represent every decimal exactly — the classic demonstration is that 0.1 + 0.2 does not equal 0.3. For amounts of money, either store paise as whole numbers with NumberInt or NumberLong, or use NumberDecimal, which stores an exact decimal value. Never store a price as a floating point rupee amount in a system that adds up invoices.
db.orders.insertOne({
orderNo: "ORD-1001",
customer: "Ananya Sharma",
placedAt: new Date(), // real date, not a string
dueBy: ISODate("2026-02-01T00:00:00Z"),
amount: NumberDecimal("1299.50"), // exact decimal for money
quantity: NumberInt(3), // 32-bit integer, not a double
paid: false, // boolean, not "false"
tags: ["electronics", "prepaid"], // array
address: { city: "Pune", pincode: "411001" }, // nested object
couponUsed: null // explicitly "no value"
}) nulland a missing field are not the same thing.{ couponUsed: null }says "we know there was no coupon"; a document with nocouponUsedfield at all says "we never recorded this". Queries can tell them apart with$exists, so decide which one you mean.
insertMany: Many Documents in One Trip
insertMany() takes an array of documents. It is not just shorter to type — it is genuinely faster, because the cost of a database operation is dominated by the network round trip, not by the writing itself. Sending 500 documents in one call and sending them in 500 calls do roughly the same amount of disk work, but the second version pays 500 network round trips instead of one. On a cloud database with a few tens of milliseconds of latency, that is the difference between a second and half a minute.
The return value is an object of insertedIds, keyed by each document's position in the array you passed. Very large arrays are split into batches by the driver automatically, so you do not have to chunk them by hand.
db.products.insertMany([
{ name: "Laptop", price: 54999, category: "Electronics", stock: 12 },
{ name: "Mouse", price: 799, category: "Electronics", stock: 140 },
{ name: "Desk", price: 6500, category: "Furniture", stock: 8 },
{ name: "Chair", price: 4200, category: "Furniture", stock: 0 },
{ name: "Monitor", price: 12999, category: "Electronics", stock: 25 }
])
// {
// acknowledged: true,
// insertedIds: { '0': ObjectId("..."), '1': ObjectId("..."), ... }
// }
db.products.countDocuments() // 5 Ordered vs Unordered: What Happens When One Document Fails
This is the part of insertMany that surprises people in production. By default the insert is ordered: MongoDB writes the documents in array order and stops dead at the first failure. Everything before the bad document is already saved; everything after it is never attempted. The call throws an error, so if you are not reading the error carefully you may believe nothing was inserted when in fact half the batch was.
Passing { ordered: false } changes the behaviour: MongoDB attempts every document, skips the ones that fail, and reports all the failures together at the end. For importing a CSV of five thousand rows where a handful may be duplicates, this is almost always what you want — you get the good rows in and a list of the bad ones to fix.
Use ordered inserts when later documents genuinely depend on earlier ones having succeeded. Use unordered inserts for bulk imports, seed data and log-style writes, where each document stands alone. Unordered is also slightly faster, because the server is free to run the writes in parallel.
db.users.createIndex({ email: 1 }, { unique: true })
// Ordered (the default): stops at the duplicate
try {
db.users.insertMany([
{ email: "a@example.com" },
{ email: "a@example.com" }, // duplicate -> E11000, batch stops here
{ email: "b@example.com" } // never attempted
])
} catch (e) { print(e.message) }
db.users.countDocuments() // 1
// Unordered: skips the bad one, inserts the rest
db.users.insertMany([
{ email: "c@example.com" },
{ email: "a@example.com" }, // fails, reported at the end
{ email: "d@example.com" } // still inserted
], { ordered: false }) E11000 duplicate key erroris the message you will see most often in your first year with MongoDB. It always means a unique index rejected the write. Read the rest of the message — it names the index and the exact value that clashed.
Mistakes That Bite Beginners
None of the errors below produce a compiler warning or a red line in your editor. They surface as data that looks fine until the day a report is wrong, so it is worth reading the list once now and again after you have built something.
- Passing an array to
insertOne. It expects a single object; pass an array and it will not do what you meant. UseinsertManyfor arrays. - Forgetting
awaitin Node.js. Driver methods return promises. Withoutawaityour code carries on before the write finishes, and errors vanish into an unhandled rejection. - Storing numbers as strings —
price: "799"instead ofprice: 799. Sorting then puts"1000"before"799", because that is how text sorts. - Re-running a seed script and doubling your data. Inserts are not idempotent; running the same script twice inserts everything twice unless a unique index stops it.
- Letting an array field grow without limit inside a document — every comment, every log line, every event. A document cannot exceed 16 MB, and long before that limit it becomes slow to read and update.
- Trusting user input straight into a document. Validate in your application, and add a database-level validator, before an insert writes whatever an attacker sent you.
- For a seed script you may run repeatedly, replace the plain insert with an upsert:
db.products.updateOne({ sku: "AB-1" }, { $set: { ... } }, { upsert: true })creates the document the first time and updates it every time after. You will meet upserts properly in the lesson on updating documents.
