Menjalankan CRUD dari aplikasi tampak seperti urusan satu baris — panggil insertOne, selesai. Kenyataannya, kode produksi yang sehat mengikuti beberapa kebiasaan yang tidak terlihat di contoh singkat: filter dibangun sebagai objek terstruktur, hasil tulis diperiksa, banyak data masuk sebagai batch, dan hasil besar dibaca lewat cursor. Chapter ini mengeksekusi pola-pola itu satu per satu di mongosh — yang sintaksnya identik dengan driver — lalu memetakan padanan bentuk hasilnya di Node.js dan Python.

Filter dan Projection Tetap Objek #

Aturan pertama berasal dari keamanan: input pengguna tidak pernah boleh dirangkai menjadi teks query. Filter dan projection dibangun sebagai objek, dan nilai dari luar masuk sebagai data di dalamnya — bukan sebagai kode. Kita simulasikan request pengguna lalu susun filternya:

js
db = db.getSiblingDB("latihan_crud")
db.pesanan.drop()
db.pesanan.insertMany([
  { kode: "PSN-001", pelanggan: "Budi", status: "dikirim", total: 120000 },
  { kode: "PSN-002", pelanggan: "Siti", status: "baru", total: 75000 },
  { kode: "PSN-003", pelanggan: "Budi", status: "baru", total: 53000 }
])

// parameter dari request pengguna masuk sebagai data, bukan kode query
const params = { pelanggan: "Budi", totalMinimal: 50000 }
const filter = { pelanggan: params.pelanggan, total: { $gte: params.totalMinimal } }
db.pesanan.find(filter, { kode: 1, total: 1, _id: 0 }).sort({ total: -1 }).toArray()

Output yang Dihasilkan:

text
latihan_crud
true
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ad3e1942f58cb6700085930'),
    '1': ObjectId('6ad3e1942f58cb6700085931'),
    '2': ObjectId('6ad3e1942f58cb6700085932')
  }
}

[
  { kode: 'PSN-001', total: 120000 },
  { kode: 'PSN-003', total: 53000 }
]

Struktur filter ($gte, nama field) sepenuhnya ditentukan kode; nilai dari params hanya mengisi bagian data. Injeksi query menjadi tidak mungkin karena tidak ada jalur bagi input untuk berubah menjadi operator — prinsip yang sama dengan prepared statement di basis data relasional, dan dibahas lebih jauh dari sisi serangannya di Section Keamanan.

Acknowledgment Dicek, Bukan Diasumsikan #

Setiap operasi tulis mengembalikan objek hasil — dan kode yang sehat membacanya sebelum menganggap sukses. Objek itu berbeda per operasi:

OperasiField hasil yang penting
insertOneacknowledged, insertedId
updateOneacknowledged, matchedCount, modifiedCount, upsertedId
deleteManyacknowledged, deletedCount

matchedCount adalah yang paling sering diabaikan padahal paling informatif: ia menghitung document yang cocok dengan filter, sebelum perubahan diterapkan. Kita periksa kasus filter yang tidak menemukan apa pun:

js
const hasil = db.pesanan.updateOne({ kode: "PSN-999" }, { $set: { status: "dikirim" } })
printjson({
  acknowledged: hasil.acknowledged,
  matchedCount: hasil.matchedCount,
  modifiedCount: hasil.modifiedCount
})
if (hasil.matchedCount === 0) {
  print("tidak ada pesanan yang cocok — laporkan ke pemanggil, jangan dibiarkan")
}

Output yang Dihasilkan:

text
{
  acknowledged: true,
  matchedCount: 0,
  modifiedCount: 0
}

tidak ada pesanan yang cocok — laporkan ke pemanggil, jangan dibiarkan

Operasi “berhasil” tanpa error, tetapi tidak mengubah apa pun — tanpa pemeriksaan matchedCount, ketidakcocokan kode pesanan seperti ini akan lolos diam-diam dan pengguna menerima balasan sukses yang keliru. Catatan tambahan: acknowledged: false hanya muncul bila write concern w: 0 dipilih — aplikasi yang menuntut kepastian tidak memakai mode itu.

Batch untuk Banyak, Cursor untuk Besar #

Menyisipkan banyak document lewat perulangan insertOne berarti N round-trip ke server; insertMany memaketkannya menjadi sedikit perintah. Sementara itu di sisi baca, toArray() memuat seluruh hasil ke memori sekaligus — tepat untuk puluhan document, berisiko untuk jutaan. Pola yang benar untuk hasil besar: iterasi cursor.

js
const batch = []
for (let i = 4; i <= 8; i++) {
  batch.push({ kode: "PSN-00" + i, pelanggan: "Dewi", status: "baru", total: 10000 * i })
}
db.pesanan.insertMany(batch)

let jumlah = 0, totalNilai = 0
db.pesanan.find({ status: "baru" }).forEach(d => { jumlah++; totalNilai += d.total })
printjson({ statusDifilter: "baru", jumlah: jumlah, totalNilai: totalNilai })

Output yang Dihasilkan:

text
5
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ad3e1942f58cb6700085933'),
    '1': ObjectId('6ad3e1942f58cb6700085934'),
    '2': ObjectId('6ad3e1942f58cb6700085935'),
    '3': ObjectId('6ad3e1942f58cb6700085936'),
    '4': ObjectId('6ad3e1942f58cb6700085937')
  }
}

{
  statusDifilter: 'baru',
  jumlah: 7,
  totalNilai: 428000
}

Baris 5 di awal adalah gema REPL dari perulangan for — nilai penutup batch.push() terakhir, yaitu panjang array. Angka yang penting ada di ringkasan akhir: filter status: "baru" menemukan 7 document (dua dari data awal, lima dari batch baru) dengan total nilai 428000. forEach pada cursor memproses document demi document tanpa menumpuk semuanya di memori — di Node.js padanannya cursor.forEach() atau for await (const doc of cursor), di PyMongo iterasi for doc in cursor:. insertMany secara bawaan bersifat ordered: bila satu document gagal, sisanya berhenti — perilaku yang bisa diubah dengan { ordered: false } saat kecepatan lebih penting daripada urutan.

Konversi Tipe Antar Dunia #

Driver menerjemahkan tipe BSON ke tipe bahasa — dan sebaliknya. Tiga yang paling sering mengejutkan:

KonsepmongoshNode.jsPython (PyMongo)
ObjectId baruObjectId()new ObjectId()ObjectId()
Tanggal kininew Date()new Date()datetime.now(timezone.utc)
AngkaNumberInt(5), Long(5)new Int32(5), new Long(5)Int64(5)

Angka adalah jebakan klasik: 5 telanjang di mongosh dan JS adalah double 64-bit, bukan int32. Validasi bsonType: "int" yang kita pasang di validator collection akan menolaknya bila aplikasi lupa mengirim Int32/Int64 secara eksplisit — gejalanya muncul sebagai error 121 yang sudah kita bedah di Chapter Error Handling.

Terakhir, kita rapikan database latihan:

js
db = db.getSiblingDB("latihan_crud")
db.dropDatabase()

Output yang Dihasilkan:

text
latihan_crud
{ ok: 1, dropped: 'latihan_crud' }

Rangkuman #

  • Filter dan projection dibangun sebagai objek; nilai dari pengguna masuk sebagai data di dalamnya sehingga injeksi query tidak memiliki jalur.
  • Setiap operasi tulis mengembalikan objek hasil — matchedCount: 0 adalah kegagalan senyap bila tidak diperiksa.
  • insertMany memaketkan banyak sisipan; cursor (forEach, for await, iterasi) menjaga memori tetap rendah untuk hasil besar.
  • Driver memetakan tipe BSON ke tipe bahasa: ObjectId, Date, dan angka int/double harus dikirim dengan sengaja, bukan dibiarkan default bahasa.