Learning MongoDB - Create & Read Operations (Insert & Find)
Episode 3 of 21

Learning MongoDB - Create & Read Operations (Insert & Find)

Writing your first data to MongoDB using insertOne, insertMany, and bulkWrite for mixed batch operations, then reading that data back with find, findOne, and projection to select which fields are returned.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

After understanding the data structure in episode 2, it's time to practice. Episode 3 opens the CRUD phase — Create, Read, Update, Delete — that will accompany you throughout your career as a MongoDB developer. This episode focuses on the first two operations: Create (storing data) and Read (retrieving data).

These two operations are the most frequently used in the real world. Every API endpoint that receives a POST request uses an insert behind the scenes, and almost every GET endpoint uses find. The episode 3 roadmap: first we learn the three ways to store data — insertOne, insertMany, and bulkWrite; second we learn how to retrieve data — find, findOne; and third we master projection to control the shape of the query result. Let's get started.

Insert Operations

insertOne: Inserting a Single Document

The insertOne function inserts a single document into a collection. If the collection doesn't exist, MongoDB creates it automatically. Notice how to call it:

Inserting a single document
db.users.insertOne({
  name: "Siti Rahma",
  email: "siti@example.com",
  age: 24,
  role: "student"
})

This function returns a result containing acknowledged: true and insertedId — the _id value given to the new document:

insertOne result
{
  "acknowledged": true,
  "insertedId": ObjectId("66xxxxxxxxxxxxxxxxxxxxxxxx")
}

This insertedId is the key you'll use to reference this document in subsequent operations.

insertMany: Inserting Many Documents

When you have lots of data to store at once, don't call insertOne in a loop. Use insertMany, which sends all documents in a single round-trip to the server — far faster:

Inserting many documents at once
db.products.insertMany([
  { name: "Mouse Wireless", price: 150000, category: "elektronik" },
  { name: "Keyboard Mechanical", price: 750000, category: "elektronik" },
  { name: "Kaos Polos", price: 99000, category: "fashion" }
])

The result contains an insertedIds array for every document. Important note: insertMany is ordered by default — if one document fails in the middle, the rest aren't processed. If you want successful ones to remain stored, add the option { ordered: false }.

bulkWrite: Mixed Batch Operations

bulkWrite is the most flexible feature: a single call can hold a mix of insert, update, and delete operations at once. This is very useful for data synchronization processes where you must add, update, and delete in a single batch:

Bulk write with mixed operations
db.products.bulkWrite([
  { insertOne: { document: { name: "Headset", price: 300000, category: "elektronik" } } },
  { updateOne: { filter: { name: "Kaos Polos" }, update: { $set: { price: 89000 } } } },
  { deleteOne: { filter: { name: "Mouse Wireless" } } }
])

The result gives you a summary of how many inserts, updates, and deletes were executed. bulkWrite sends all operations in a minimal number of round-trips — an investment that pays off at large data volumes.

Read Operations

find: Retrieving Many Documents

The find function is the primary way to read data. It accepts a filter (matching criteria) and returns a cursor that can be iterated:

Retrieving all documents
db.products.find({})

find({}) with an empty filter returns all documents. With a filter, you limit the results based on criteria:

Filtering by category
db.products.find({ category: "elektronik" })

Both commands above return all matching documents — the result is a cursor, not an array. To see the results in mongosh, the cursor is iterated automatically; in an application, you need to call .toArray() or iterate the cursor.

findOne: Retrieving a Single Document

findOne returns the first document matching the filter, or null if none exists. It's the ideal companion for operations that need a single record:

Retrieving a single document
db.users.findOne({ email: "siti@example.com" })

Key difference: find returns a cursor (can be empty without error), while findOne directly returns a document or null. For "check if this user exists" or "get this user's profile" cases, findOne is far more practical.

Projection: Selecting the Fields Returned

As documents grow larger (potentially dozens of fields), returning everything on every query wastes bandwidth and memory. Projection is the second parameter of find that determines which fields are returned:

Projection: only certain fields
db.users.find({ role: "student" }, { name: 1, email: 1 })

A value of 1 means include that field, 0 means exclude it. To exclude _id (which is always included by default), set _id: 0:

Projection excluding _id
db.users.find({}, { name: 1, email: 1, _id: 0 })

Info

Projection rules: you can mix 1 and 0 in a single projection as long as the only field set to 0 is _id. For example, { name: 1, email: 1, _id: 0 } is valid, but { name: 1, address: 0 } is not — MongoDB rejects mixing inclusion and exclusion apart from _id. Pick one style: include everything you need, or exclude what you don't need.

Complete Create + Read CRUD Practice

Let's tie everything learned together in a real scenario: a small online store. First we store several products, then we read them in various ways:

Complete insert and find scenario
db.products.insertMany([
  { name: "Laptop Gaming", price: 15000000, category: "elektronik", stock: 5 },
  { name: "Meja Kantor", price: 1200000, category: "furniture", stock: 10 },
  { name: "Sneaker", price: 450000, category: "fashion", stock: 25 }
])
 
db.products.findOne({ name: "Sneaker" })
 
db.products.find({ stock: { $gt: 8 } }, { name: 1, price: 1, _id: 0 })

Notice the $gt operator in the last query — that's a comparison operator we'll dissect in depth in episode 5. For now, just understand that { stock: { $gt: 8 } } means "stock greater than 8".

Warning

Be careful with find({}) on a large collection without a filter. Without a proper index, MongoDB has to scan the entire collection to return results — this is called a COLLSCAN, the main enemy of performance. We'll cover indexes and query optimization in episodes 12 and 13. For this episode, just get used to always including a selective filter.

Conclusion

In episode 3 you mastered the first two pillars of CRUD. For Create: insertOne for a single document, insertMany for many documents in one round-trip, and bulkWrite for mixed insert-update-delete batch operations. For Read: find, which returns a cursor with a filter, findOne for the first matching document, and projection to select the fields returned and exclude _id.

Key takeaways:

  • insertMany is far faster than a insertOne loop — always use it for mass data.
  • bulkWrite combines insert, update, and delete in a single call.
  • find returns a cursor; findOne returns a document or null.
  • Projection uses values 1 (include) and 0 (exclude); only _id can be excluded in include mode.
  • Always include a selective filter so queries don't become full collection scans.

In the next episode, episode 4, we finish the rest of the CRUD operations: Update & Delete Operations. You'll update documents with updateOne, updateMany, and replaceOne, learn update operators like $set, $inc, and $push, delete data with deleteOne and deleteMany, and leverage the clever upsert pattern. See you in episode 4!

Learning MongoDB - Create & Read Operations (Insert & Find) | Learning MongoDB