KevsRobots Learning Platform
By Kevin McAleer, 12 Minutes
Page last updated October 18, 2026

Last lesson we put two parts in the drawer by hand. Now letβs learn inserting properly: adding several documents at once, what that mysterious _id really is, and which kinds of values MongoDB can store. At the end of the lesson we will load the full Parts Bin, which every later lesson relies on.
Open mongosh and switch to the workshop first:
use workshop
You already know insertOne. It takes one document:
db.parts.insertOne({ name: "MG996R Servo", category: "servo", quantity: 4, price: 6.99 })
{
acknowledged: true,
insertedId: ObjectId('6ad5245dd9ebf8421d27936d')
}
insertMany takes a list of documents, wrapped in square brackets [ ] and separated by commas:
db.parts.insertMany([
{ name: "N20 Gear Motor", category: "motor", quantity: 8 },
{ name: "18650 Battery", category: "power", quantity: 6 }
])
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad5245dd9ebf8421d27936e'),
'1': ObjectId('6ad5245dd9ebf8421d27936f')
}
}
Notice the reply now says insertedIds, plural. The '0' and '1' are positions in your list (programmers count from zero), so you can tell which ID went with which part.
db.parts.countDocuments()
5
Two from last lesson plus three just now. If you added an extra part in the last Try it Yourself, you will see 6.
Use insertMany whenever you have more than one document. It sends them all to the server in one trip, which is much quicker than lots of separate insertOne calls.
Every document must have an _id field, and no two documents in a collection can share one. If you donβt supply an _id, MongoDB creates an ObjectId for you.
An ObjectId looks like random gibberish, but it is 12 bytes with a structure:
| Bytes | What it holds |
|---|---|
| First 4 | The time it was created, in seconds |
| Next 5 | A random value, unique to the program that made it |
| Last 3 | A counter that goes up by one each time |
That counter is why the IDs above look so similar: ...936d, ...936e, ...936f.
Because the time is baked in, you can ask an ObjectId when it was made. Letβs save the result of an insert in a variable (a named box that holds a value) called result. The shell prints nothing for that line, so type result afterwards to see it:
const result = db.parts.insertOne({ name: "L298N Motor Driver", category: "driver", quantity: 3 })
result
{
acknowledged: true,
insertedId: ObjectId('6ad5245dd9ebf8421d279370')
}
Now dig the timestamp out of the new ID:
result.insertedId.getTimestamp()
ISODate('2026-10-18T19:56:13.000Z')
That is the moment the part went in, in UTC (world time, shown by the Z at the end). A free βdate addedβ on every document.
MongoDB can store all the JSON types from lesson 02, plus a proper date. Letβs insert one document that uses them all:
db.parts.insertOne({
name: "Data Types Demo",
quantity: 5,
price: 4.00,
in_stock: true,
supplier: null,
tags: ["demo", "test"],
location: { shelf: "Z", bin: 9 },
added: new Date("2026-10-18")
})
findOne is like find but returns just the first match, without the surrounding list. Letβs read our demo back:
db.parts.findOne({ name: "Data Types Demo" })
{
_id: ObjectId('6ad5245dd9ebf8421d279371'),
name: 'Data Types Demo',
quantity: 5,
price: 4,
in_stock: true,
supplier: null,
tags: [ 'demo', 'test' ],
location: { shelf: 'Z', bin: 9 },
added: ISODate('2026-10-18T00:00:00.000Z')
}
| Field | Type | Notes |
|---|---|---|
name |
String | Text in quotes |
quantity |
Number | A whole number |
price |
Number | 4.00 is shown as 4 - same value, MongoDB just drops the trailing zeros |
in_stock |
Boolean | true or false, no quotes |
supplier |
Null | βNothing here yetβ |
tags |
Array | A list of values |
location |
Nested document | A document inside a document |
added |
Date | Made with new Date(...) and shown as ISODate(...) |
Always use new Date("YYYY-MM-DD") for dates rather than typing them as a string like "2026-10-18". A real date can be sorted and compared; a string is just text. new Date() with nothing inside gives you right now.
Time to clear out our experiments and load the real data. drop() deletes a whole collection, documents and all:
db.parts.drop()
true
Now paste in the full Parts Bin. It is long, so copy the whole block in one go:
use workshop
db.parts.insertMany([
{ name: "SG90 Micro Servo", category: "servo", quantity: 12, price: 2.50,
location: { shelf: "A", bin: 1 }, tags: ["servo", "5v", "smars"],
specs: { torque_kg_cm: 1.8, voltage: "4.8-6V" }, used_in: ["SMARS", "BurgerBot"],
added: new Date("2026-01-12") },
{ name: "MG996R Servo", category: "servo", quantity: 4, price: 6.99,
location: { shelf: "A", bin: 2 }, tags: ["servo", "metal gear"],
specs: { torque_kg_cm: 11, voltage: "4.8-7.2V" }, used_in: ["Robot Arm"],
added: new Date("2026-02-03") },
{ name: "HC-SR04 Ultrasonic Sensor", category: "sensor", quantity: 6, price: 1.80,
location: { shelf: "B", bin: 1 }, tags: ["sensor", "distance", "5v"],
specs: { range_cm: 400, voltage: "5V" }, used_in: ["SMARS", "BurgerBot"],
added: new Date("2026-01-12") },
{ name: "VL53L0X Time-of-Flight Sensor", category: "sensor", quantity: 4, price: 4.99,
location: { shelf: "B", bin: 2 }, tags: ["sensor", "distance", "i2c", "3v3"],
specs: { range_cm: 200, voltage: "3.3V" }, used_in: [],
added: new Date("2026-03-21") },
{ name: "Raspberry Pi Pico W", category: "board", quantity: 5, price: 6.30,
location: { shelf: "C", bin: 1 }, tags: ["microcontroller", "wifi", "micropython"],
specs: { ram_kb: 264, voltage: "3.3V" }, used_in: ["SMARS", "BurgerBot"],
added: new Date("2026-01-20") },
{ name: "Raspberry Pi 5 4GB", category: "board", quantity: 1, price: 55.00,
location: { shelf: "C", bin: 2 }, tags: ["computer", "linux"],
specs: { ram_kb: 4194304, voltage: "5V" }, used_in: ["Robot Arm"],
added: new Date("2026-02-14") },
{ name: "N20 Gear Motor", category: "motor", quantity: 8, price: 3.20,
location: { shelf: "D", bin: 1 }, tags: ["motor", "smars"],
specs: { rpm: 100, voltage: "6V" }, used_in: ["SMARS"],
added: new Date("2026-01-12") },
{ name: "L298N Motor Driver", category: "driver", quantity: 3, price: 2.99,
location: { shelf: "D", bin: 2 }, tags: ["motor driver", "5v"],
specs: { channels: 2, voltage: "5-35V" }, used_in: ["BurgerBot"],
added: new Date("2026-02-03") },
{ name: "DRV8833 Motor Driver", category: "driver", quantity: 2, price: 4.50,
location: { shelf: "D", bin: 3 }, tags: ["motor driver", "3v3"],
specs: { channels: 2, voltage: "2.7-10.8V" }, used_in: ["SMARS"],
added: new Date("2026-03-21") },
{ name: "PCA9685 Servo Driver", category: "driver", quantity: 2, price: 5.99,
location: { shelf: "D", bin: 4 }, tags: ["servo", "i2c"],
specs: { channels: 16, voltage: "3.3-5V" }, used_in: ["Robot Arm"],
added: new Date("2026-02-14") },
{ name: "18650 Battery", category: "power", quantity: 6, price: 3.50,
location: { shelf: "E", bin: 1 }, tags: ["battery", "lithium"],
specs: { capacity_mah: 2600, voltage: "3.7V" }, used_in: ["BurgerBot"],
added: new Date("2026-01-20") },
{ name: "WS2812B LED Strip 1m", category: "lighting", quantity: 0, price: 7.50,
location: { shelf: "E", bin: 2 }, tags: ["led", "neopixel", "5v"],
specs: { leds: 60, voltage: "5V" }, used_in: [],
added: new Date("2026-03-02") },
{ name: "M3 Screws (pack of 100)", category: "hardware", quantity: 3, price: 4.00,
location: { shelf: "F", bin: 1 }, tags: ["hardware", "m3"],
specs: { length_mm: 10 }, used_in: ["SMARS", "BurgerBot", "Robot Arm"],
added: new Date("2026-01-12") }
])
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad5245dd9ebf8421d279372'),
'1': ObjectId('6ad5245dd9ebf8421d279373'),
'2': ObjectId('6ad5245dd9ebf8421d279374'),
'3': ObjectId('6ad5245dd9ebf8421d279375'),
'4': ObjectId('6ad5245dd9ebf8421d279376'),
'5': ObjectId('6ad5245dd9ebf8421d279377'),
'6': ObjectId('6ad5245dd9ebf8421d279378'),
'7': ObjectId('6ad5245dd9ebf8421d279379'),
'8': ObjectId('6ad5245dd9ebf8421d27937a'),
'9': ObjectId('6ad5245dd9ebf8421d27937b'),
'10': ObjectId('6ad5245dd9ebf8421d27937c'),
'11': ObjectId('6ad5245dd9ebf8421d27937d'),
'12': ObjectId('6ad5245dd9ebf8421d27937e')
}
}
The use workshop line at the top just makes sure you are in the right place (if you already are, mongosh says already on db workshop). Check the count:
db.parts.countDocuments()
13
Thirteen parts, from servos to screws. This is the data the rest of the course uses. If you ever mess it up, you can reset by running db.parts.drop() and then pasting that block again.
Here is the flip side of a flexible schema. Letβs add a charger board, but misspell quantity:
db.parts.insertOne({ name: "TP4056 Charger Board", category: "power", quantty: 5, price: 0.80 })
{
acknowledged: true,
insertedId: ObjectId('6ad5245dd9ebf8421d27937f')
}
No error. MongoDB happily stored a brand new field called quantty:
db.parts.findOne({ name: "TP4056 Charger Board" })
{
_id: ObjectId('6ad5245dd9ebf8421d27937f'),
name: 'TP4056 Charger Board',
category: 'power',
quantty: 5,
price: 0.8
}
Now count the parts that have exactly 5 in stock. You can pass countDocuments a filter, which we will explore properly next lesson:
db.parts.countDocuments({ quantity: 5 })
1
Only the Pico W. The charger board is invisible to any search on quantity, because as far as MongoDB knows it doesnβt have one. Mistakes like this quietly hide data, so double-check field names, and in lesson 11 we will add rules that reject them.
Letβs remove the broken document. deleteOne gets a full lesson of its own later, but for now:
db.parts.deleteOne({ name: "TP4056 Charger Board" })
{ acknowledged: true, deletedCount: 1 }
We are back to 13 parts.
You can choose your own _id instead of an ObjectId, as long as it is unique. Letβs try it in a throwaway collection called scratch:
db.scratch.insertOne({ _id: "servo-1", name: "SG90 Micro Servo" })
{ acknowledged: true, insertedId: 'servo-1' }
Do it again with the same _id and MongoDB refuses:
db.scratch.insertOne({ _id: "servo-1", name: "MG996R Servo" })
Uncaught MongoServerError: E11000 duplicate key error collection: workshop.scratch index: _id_ dup key: { _id: "servo-1" }
E11000 is the error code for βduplicate keyβ, and you will see it whenever something that must be unique isnβt.
What happens if a duplicate turns up in the middle of an insertMany? Letβs clear scratch and find out:
db.scratch.drop()
db.scratch.insertMany([
{ _id: 1, name: "first" },
{ _id: 2, name: "second" },
{ _id: 1, name: "duplicate!" },
{ _id: 3, name: "third" }
])
Uncaught:
MongoBulkWriteError: E11000 duplicate key error collection: workshop.scratch index: _id_ dup key: { _id: 1 }
Result: BulkWriteResult {
insertedCount: 2,
...
db.scratch.find()
[ { _id: 1, name: 'first' }, { _id: 2, name: 'second' } ]
By default inserts are ordered: MongoDB works down the list and stops at the first error. The first two went in, the duplicate failed, and third was never even tried.
Add { ordered: false } and MongoDB carries on past errors instead:
db.scratch.drop()
db.scratch.insertMany([
{ _id: 1, name: "first" },
{ _id: 2, name: "second" },
{ _id: 1, name: "duplicate!" },
{ _id: 3, name: "third" }
], { ordered: false })
You still get the same error report, but this time it says insertedCount: 3:
db.scratch.find()
[
{ _id: 1, name: 'first' },
{ _id: 2, name: 'second' },
{ _id: 3, name: 'third' }
]
Tidy up the scratch collection so only parts is left:
db.scratch.drop()
insertMany to add two parts of your own to scratch, then use find() to see their ObjectIds. Which characters differ between them?new Date() on its own. What does it show?{ _id: "servo-1" } into scratch twice. What is the error code?scratch and checking db.parts.countDocuments() is still 13.Problem: MongoBulkWriteError: E11000 duplicate key error when loading the Parts Bin.
Solution: Run db.parts.drop() first, then paste the block again.
Why: If you pasted it twice, or something went wrong halfway, the collection may already hold some of the documents.
Problem: Your count is 26, not 13.
Solution: Drop and reload as above.
Why: Without our own _ids, every paste creates 13 new documents with new ObjectIds, so pasting twice gives you two of everything.
Problem: After pasting, the shell sits at a | prompt and never runs the command.
Solution: A bracket isnβt closed. Press Ctrl+C to cancel, then copy the whole block again, right down to the final ]).
Why: mongosh waits for more lines until every (, [ and { has its partner.
Problem: Dates sort or compare strangely.
Solution: Store them with new Date("2026-01-12"), not as the string "2026-01-12".
Why: Strings are just text. Only real dates can be compared as dates.
insertOne adds one document; insertMany adds a list in one trip_id, and an ObjectId has its creation time inside (getTimestamp())ordered: falseworkshop.partsNext, we will start asking the Parts Bin questions with find().
You can use the arrows β β on your keyboard to navigate between lessons.
Comments