Chapter 03 · CRUD Fundamentals: Insert, Find, Projection, Sort, Limit, and Delete

Build a CRUD Lab and Verify Every Operation with Deterministic Queries

Combine the chapter into one auditable CRUD runbook that records exact inputs, results, batches, partial failures, and postconditions.

Beginner110–140 minutesChapter CRUD capstone runbookMongoDB Community Server 8.3.8 · mongosh 2.10.0 · PyMongo 4.17.0Last reviewed: September 2026

Learning outcomes

This capstone turns the chapter's isolated operations into a reproducible AtlasMart data task: reset a fixture, insert known records, prove partial-success handling, query with a precise contract, consume a cursor in bounded batches, delete only staged records after a preflight, and verify every postcondition. The goal is not command memorization; it is an evidence-driven CRUD runbook.

01

Build one deterministic fixture that exercises insert, read, projection, sort, limit, cursor, and delete semantics.

02

Record before state, operation inputs/options, result metadata, and after state for every mutation.

03

Use stable _id values and total sort orders so repeated verification is comparable.

04

Handle expected duplicate-key failure without confusing partial success with rollback.

05

Finish with a reusable checklist for safe CRUD work in scripts and services.

Chapter 03 reproducible baseline

Mandatory labs use a disposable loopback-only standalone mongodb/mongodb-community-server:8.3.8-ubuntu2204-slim with a dedicated AtlasMart collection and explicit reset commands. Driver examples pin pymongo==4.17.0. The standalone is intentionally unauthenticated only for these short-lived local exercises; do not publish it beyond 127.0.0.1. Docker commands use syntax accepted directly by ordinary shells; if the first cleanup reports that the container does not exist, that is harmless. Retryable-write behavior that requires a replica set or sharded cluster is explained but not claimed for this standalone.

Generation-time execution note

Docker, mongod, mongosh, and PyMongo are not available in this generation environment. Commands were checked against current official MongoDB Server and PyMongo documentation, but product commands were not executed here. Expected-output blocks describe stable fields and relationships to verify; they are not fabricated captured transcripts.

1. Define the CRUD contract before executing commands

Step Exact contract Evidence
Reset Dedicated atlasmart.crud_lab only Collection absent/empty before seed
Insert Known IDs for fixture plus one generated-ID demonstration Acknowledged result, inserted IDs, sorted stored rows
Read Explicit filter + projection + {priceCents:1,_id:1} Returned documents and count
Cursor Batch size 2, deterministic order First/next batches and exhaustion
Delete Tenant + source + status predicate Preflight count/sample, deletedCount, zero-match postcondition

This table is the operational specification. It makes reviews possible because every destructive step has a corresponding read-only proof and every read has an explicit ordering contract.

2. One mongosh runbook with deliberately observable state

mongosh · complete Chapter 03 CRUD runbook
use atlasmartdb.crud_lab.drop();printjson({phase:"reset", count:db.crud_lab.countDocuments({})});const generated = db.crud_lab.insertOne({sku:"generated",tenantId:"tenant-demo",kind:"catalog",active:true,priceCents:7000});printjson({phase:"insertOne", acknowledged:generated.acknowledged, insertedId:generated.insertedId});try {  db.crud_lab.insertMany([    {_id:"p1",sku:"cam-a",tenantId:"tenant-demo",kind:"catalog",active:true,category:"camera",priceCents:9000},    {_id:"p2",sku:"cam-b",tenantId:"tenant-demo",kind:"catalog",active:true,category:"camera",priceCents:9000},    {_id:"p2",sku:"duplicate",tenantId:"tenant-demo",kind:"catalog",active:true,category:"camera",priceCents:1},    {_id:"p3",sku:"cam-c",tenantId:"tenant-demo",kind:"catalog",active:true,category:"camera",priceCents:12000},    {_id:"stage-1",sku:"staged-a",tenantId:"tenant-demo",kind:"staging",source:"import-03",status:"staging",priceCents:5000},    {_id:"stage-2",sku:"staged-b",tenantId:"tenant-demo",kind:"staging",source:"import-03",status:"staging",priceCents:6000}  ], {ordered:false});} catch(e) {  printjson({phase:"insertManyError",code:e.code,result:e.result});}printjson({phase:"afterInsert", docs:db.crud_lab.find({}, {_id:1,sku:1,kind:1}).sort({_id:1}).toArray()});const filter={kind:"catalog",active:true,category:"camera",priceCents:{$gte:8000,$lte:12000}};const projection={_id:1,sku:1,priceCents:1};const result=db.crud_lab.find(filter,projection).sort({priceCents:1,_id:1}).limit(10).toArray();printjson({phase:"read",filter,projection,returned:result.length,result});const first=db.runCommand({find:"crud_lab",filter:{kind:"catalog"},sort:{_id:1},batchSize:2});printjson({phase:"cursorFirst",id:first.cursor.id,batch:first.cursor.firstBatch.map(x=>x._id)});let id=first.cursor.id;while(id.toString() !== "0"){  const more=db.runCommand({getMore:id,collection:"crud_lab",batchSize:2});  printjson({phase:"cursorMore",id:more.cursor.id,batch:more.cursor.nextBatch.map(x=>x._id)});  id=more.cursor.id;}const deleteFilter={tenantId:"tenant-demo",kind:"staging",source:"import-03",status:"staging"};const deleteBefore=db.crud_lab.countDocuments(deleteFilter);printjson({phase:"deletePreflight",filter:deleteFilter,count:deleteBefore,sample:db.crud_lab.find(deleteFilter,{_id:1,sku:1}).sort({_id:1}).toArray()});const del=db.crud_lab.deleteMany(deleteFilter);printjson({phase:"deleteResult",acknowledged:del.acknowledged,deletedCount:del.deletedCount});printjson({phase:"final",sameFilterRemaining:db.crud_lab.countDocuments(deleteFilter),all:db.crud_lab.find({}, {_id:1,sku:1,kind:1}).sort({_id:1}).toArray()});

Every phase emits a compact evidence record. A production implementation would normally send equivalent fields to structured logs/traces rather than relying on terminal output. Be careful not to log sensitive document contents, credentials, or tenant data indiscriminately.

3. The driver version should preserve the same invariants

The API surface changes from camelCase mongosh methods to PyMongo methods and result classes, but the correctness model is the same. Filters remain BSON documents represented as Python dictionaries, sorted reads need a unique tie-breaker, cursors should be iterated/closed, and write result objects/errors must be inspected.

Python · same CRUD correctness model
from pymongo import MongoClient, ASCENDINGfrom pymongo.errors import BulkWriteErrorURI = "mongodb://127.0.0.1:27031/?directConnection=true"client = MongoClient(URI, serverSelectionTimeoutMS=3000)coll = client.atlasmart.crud_labcoll.drop()one = coll.insert_one({"sku":"generated", "kind":"catalog", "active":True, "priceCents":7000})print("insert_one", one.acknowledged, one.inserted_id)try:    coll.insert_many([        {"_id":"p1","sku":"cam-a","kind":"catalog","active":True,"category":"camera","priceCents":9000},        {"_id":"p2","sku":"cam-b","kind":"catalog","active":True,"category":"camera","priceCents":9000},        {"_id":"p2","sku":"duplicate","kind":"catalog","active":True,"category":"camera","priceCents":1},        {"_id":"p3","sku":"cam-c","kind":"catalog","active":True,"category":"camera","priceCents":12000},    ], ordered=False)except BulkWriteError as exc:    print("partial", exc.details.get("nInserted"), [(e.get("index"),e.get("code")) for e in exc.details.get("writeErrors",[])])flt = {"kind":"catalog","active":True,"category":"camera","priceCents":{"$gte":8000,"$lte":12000}}with coll.find(flt,{"_id":1,"sku":1,"priceCents":1}).sort([("priceCents",ASCENDING),("_id",ASCENDING)]).batch_size(2) as cur:    for doc in cur:        print("read", doc)client.close()

The test should assert state, not just “no exception.” For example, assert the persisted IDs after the unordered error, the exact ordered IDs returned by the query, and the zero remaining count after a scoped delete. These assertions transform examples into regression tests.

4. Failure interpretation matrix

Signal What it proves What to do next
acknowledged:true The server acknowledged under configured write concern Verify business postcondition; do not infer unrelated side effects
Bulk write error + inserted count > 0 Partial success occurred Reconcile successful IDs and failed indexes; do not replay blindly
deletedCount:0 No document was deleted by that call Re-check predicate and concurrent state
Cursor id nonzero More server-side results may remain Iterate/getMore or close when finished
Same delete filter count = 0 afterward Observed postcondition in current state Record evidence; consider concurrency/topology requirements

5. Run the complete disposable lab

shell · start disposable MongoDB on 127.0.0.1:27031
docker rm -f atlasmart-mongo-ch03-l5docker run --name atlasmart-mongo-ch03-l5 -p 127.0.0.1:27031:27017 -d mongodb/mongodb-community-server:8.3.8-ubuntu2204-slimdocker logs atlasmart-mongo-ch03-l5 --tail 25
shell · run the complete lab
# Save the mongosh runbook from the lesson as chapter03_crud_lab.js, then run:mongosh "mongodb://127.0.0.1:27031/atlasmart?directConnection=true" --quiet chapter03_crud_lab.js# Optional driver path after creating/activating a virtual environment:python -m pip install "pymongo==4.17.0"python chapter03_crud_lab.py

Verification checklist

  • Exactly one generated _id is captured and used to verify the inserted document.
  • The duplicate-key batch reports partial success and the persisted IDs are inspected afterward.
  • The read contract prints filter, projection, deterministic sort result, and returned count.
  • The cursor produces multiple bounded batches and reaches exhaustion.
  • The delete preflight and deletedCount agree in this quiescent fixture, the same filter returns zero afterward, and catalog rows survive.
  • All mutation scope is confined to atlasmart.crud_lab in the disposable container.

Check your understanding

  1. What is the chapter-wide rule for every mutation?
  2. Why does the fixture use stable string _id values for most rows?
  3. Why is a thrown BulkWriteError not enough information?
  4. What makes a paginated read deterministic?
  5. What is still missing before calling this production-ready?
Review the answers

Record the before state/scope, exact operation inputs and options, result metadata, and an explicit after-state verification.

They make expected ordering and reconciliation deterministic while a separate insert still demonstrates generated ObjectId behavior.

It does not by itself tell you whether earlier or independent operations succeeded; inspect counts/error indexes and persisted state.

An explicit total sort order, typically ending with a unique tie-breaker such as _id, plus a precise filter/projection.

Authentication/TLS, topology-specific concerns and retries, indexes/query plans, monitoring, backups, tenant authorization, load testing, and later-course operational controls must be designed and verified.

bash · cleanup/reset
docker rm -f atlasmart-mongo-ch03-l5

Chapter 04 now deepens the query language: arrays, nested fields, null/missing semantics, and expression-based predicates.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.