CRUD Operations
Create, read, update, and delete documents using insert, find, update, and delete methods.
Inserting Documents
Use insertOne for a single document or insertMany for batches. MongoDB assigns an ObjectId to _id automatically unless you provide one. Batch inserts are significantly faster than repeated single inserts because they reduce round trips.
Validate data in your application layer before insert, and consider schema validation at the collection level for defense in depth. Duplicate key errors occur when _id or unique index constraints are violated.
db.users.insertOne({ name: "Grace Hopper", email: "grace@example.com" })
db.users.insertMany([
{ name: "Alan Turing", email: "alan@example.com" },
{ name: "Katherine Johnson", email: "katherine@example.com" }
])Reading with find()
The find method returns a cursor over matching documents. Pass a filter document to narrow results and a projection to limit returned fields. Use limit and sort to control result size and ordering.
findOne returns a single document or null. For primary key lookups by _id, findOne is the most efficient pattern when backed by the default _id index.
- Cursors are lazy; documents are fetched as you iterate
- Use explain("executionStats") to verify index usage on hot queries
- Avoid returning large embedded arrays when only a subset is needed
// Find active users, return only name and email
db.users.find(
{ status: "active" },
{ projection: { name: 1, email: 1, _id: 0 } }
).sort({ name: 1 }).limit(10)
db.users.findOne({ _id: ObjectId("507f1f77bcf86cd799439011") })Updating Documents
updateOne and updateMany accept a filter and an update document with operators such as $set, $inc, and $push. Replace-style updates (without operators) overwrite the entire document except _id—use them cautiously.
findOneAndUpdate atomically finds and modifies a document, returning the pre- or post-update document depending on options. This is ideal for counters, queues, and optimistic concurrency patterns.
db.users.updateOne(
{ email: "ada@example.com" },
{ $set: { lastLogin: new Date() }, $inc: { loginCount: 1 } }
)
db.inventory.findOneAndUpdate(
{ sku: "SKU-1001" },
{ $inc: { quantity: -1 } },
{ returnDocument: "after" }
)Deleting Documents
deleteOne removes the first matching document; deleteMany removes all matches. Always test your filter with find before running a destructive delete in production.
Soft deletes—marking documents with a deletedAt field instead of removing them—preserve audit history and simplify recovery. Pair soft deletes with partial indexes that exclude deleted records from unique constraints.
db.users.deleteOne({ email: "spam@example.com" })
// Soft delete pattern
db.users.updateOne(
{ _id: userId },
{ $set: { deletedAt: new Date(), status: "deleted" } }
)Write Concern and Idempotency
Write concern controls how many replica set members must acknowledge a write before the operation returns. w: "majority" is the default for most production workloads and prevents rolled-back writes after elections.
Design upsert and replace operations to be idempotent where possible, especially in retry-heavy environments. Use unique business keys with upsert filters so duplicate retries do not create duplicate records.
- Use ordered: false on insertMany when individual failures should not stop the batch
- Prefer $set over full document replacement for partial updates
- Log filter criteria for bulk update and delete operations in admin scripts