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:
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:
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:
| Operasi | Field hasil yang penting |
|---|---|
insertOne | acknowledged, insertedId |
updateOne | acknowledged, matchedCount, modifiedCount, upsertedId |
deleteMany | acknowledged, 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:
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:
{
acknowledged: true,
matchedCount: 0,
modifiedCount: 0
}
tidak ada pesanan yang cocok — laporkan ke pemanggil, jangan dibiarkanOperasi “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.
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:
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:
| Konsep | mongosh | Node.js | Python (PyMongo) |
|---|---|---|---|
ObjectId baru | ObjectId() | new ObjectId() | ObjectId() |
| Tanggal kini | new Date() | new Date() | datetime.now(timezone.utc) |
| Angka | NumberInt(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:
db = db.getSiblingDB("latihan_crud")
db.dropDatabase()Output yang Dihasilkan:
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: 0adalah kegagalan senyap bila tidak diperiksa. insertManymemaketkan 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.