Koneksi sudah tersedia — sekarang aplikasi toko buku kita perlu bekerja: mencatat judul baru, menampilkan katalog, mengubah harga, dan menghapus stok lama. Empat operasi itu adalah CRUD, dan di driver Node.js semuanya berbentuk method pada handle collection: insertOne(), find(), updateOne(), hingga deleteMany(). Dua hal khas sisi JavaScript perlu dikuasai: setiap operasi tulis mengembalikan object hasil yang bisa diperiksa, dan _id yang dihasilkan driver bertipe ObjectId — bukan string. Chapter ini menjalankan seluruh siklus CRUD pada collection buku dengan data yang sama sepanjang Section.

Method dan Bentuk Hasilnya #

Setiap method tulis mengembalikan object ringkasan dari server. Field-field inilah yang dipakai aplikasi untuk memastikan pekerjaan benar-benar terjadi:

MethodField hasil yang penting
insertOne()acknowledged, insertedId (satu ObjectId)
insertMany()acknowledged, insertedCount, insertedIds (peta indeks → ObjectId)
updateOne() / updateMany()acknowledged, matchedCount, modifiedCount, upsertedCount
deleteOne() / deleteMany()acknowledged, deletedCount
findOne()satu document, atau null bila tidak ada yang cocok
find()FindCursor — dikonsumsi dengan toArray() atau iterasi

acknowledged mencerminkan write concern: true berarti server sudah mengonfirmasi penulisan sebelum menjawab. Bedakan pula matchedCount (berapa document yang cocok dengan filter) dari modifiedCount (berapa yang benar-benar berubah) — nilai yang cocok belum tentu berubah bila isinya sudah sama.

Satu File, Satu Siklus CRUD #

File berikut menuntaskan seluruh siklus pada collection buku, dari insertMany sampai deleteMany, dengan setiap hasil dicetak:

js
// crud-toko-buku.js — operasi CRUD lengkap pada collection buku
// Jalankan: MONGODB_URI="mongodb://127.0.0.1:27017/?directConnection=true" node crud-toko-buku.js
const { MongoClient } = require("mongodb")

const uri = process.env.MONGODB_URI ?? "mongodb://127.0.0.1:27017/?directConnection=true"

async function main() {
  const client = new MongoClient(uri)
  await client.connect()
  const buku = client.db("latihan_js").collection("buku")
  await buku.drop().catch(() => {}) // reset agar contoh bisa diulang

  // ===== CREATE =====
  const r1 = await buku.insertMany([
    { judul: "Laskar Pelangi", penulis: "Andrea Hirata", harga: 95000, stok: 12, genre: "fiksi" },
    { judul: "Bumi Manusia", penulis: "Pramoedya A. Toer", harga: 110000, stok: 8, genre: "fiksi" },
    { judul: "Atomic Habits", penulis: "James Clear", harga: 125000, stok: 20, genre: "pengembangan diri" },
    { judul: "Filosofi Teras", penulis: "Henry Manampiring", harga: 90000, stok: 15, genre: "pengembangan diri" },
  ])
  console.log("insertMany:", r1.acknowledged, "| insertedCount:", r1.insertedCount)

  const r2 = await buku.insertOne({ judul: "Rumah Kaca", penulis: "Pramoedya A. Toer", harga: 105000, stok: 5, genre: "fiksi" })
  console.log("insertOne :", r2.acknowledged, "| insertedId:", r2.insertedId)

  // ===== READ =====
  const semua = await buku.find({ genre: "fiksi" }).toArray()
  console.log("find+toArray (genre fiksi):", semua.map(d => d.judul))

  const satu = await buku.findOne({ judul: "Atomic Habits" }, { projection: { _id: 0, judul: 1, harga: 1 } })
  console.log("findOne (Atomic Habits):", satu)

  // ===== UPDATE =====
  const r3 = await buku.updateOne({ judul: "Bumi Manusia" }, { $set: { harga: 120000 } })
  console.log("updateOne :", "matched:", r3.matchedCount, "| modified:", r3.modifiedCount, "| upserted:", r3.upsertedCount)

  const r4 = await buku.updateMany({ genre: "fiksi" }, { $inc: { stok: -1 } })
  console.log("updateMany:", "matched:", r4.matchedCount, "| modified:", r4.modifiedCount, "| upserted:", r4.upsertedCount)

  // ===== DELETE =====
  const r5 = await buku.deleteOne({ judul: "Rumah Kaca" })
  console.log("deleteOne :", "deletedCount:", r5.deletedCount)

  const r6 = await buku.deleteMany({ stok: { $lt: 10 } })
  console.log("deleteMany:", "deletedCount:", r6.deletedCount)

  console.log("sisa buku :", await buku.countDocuments())

  await client.close()
}

main().catch(err => {
  console.error("GAGAL:", err.name, "-", err.message)
  process.exit(1)
})
bash
MONGODB_URI="mongodb://127.0.0.1:27017/?directConnection=true" node crud-toko-buku.js

Output yang Dihasilkan:

text
insertMany: true | insertedCount: 4
insertOne : true | insertedId: new ObjectId('6ad3eec08ea4d11914c5ba24')
find+toArray (genre fiksi): [ 'Laskar Pelangi', 'Bumi Manusia', 'Rumah Kaca' ]
findOne (Atomic Habits): { judul: 'Atomic Habits', harga: 125000 }
updateOne : matched: 1 | modified: 1 | upserted: 0
updateMany: matched: 3 | modified: 3 | upserted: 0
deleteOne : deletedCount: 1
deleteMany: deletedCount: 1
sisa buku : 3

Mari kita bedah baris-baris kuncinya. buku.drop().catch(() => {}) menghapus collection dari run sebelumnya sehingga contoh selalu bisa diulang dengan hasil yang sama. insertMany() menerima array object biasa dan membalas insertedIds berbentuk peta '0', '1', … — indeks array asal menunjuk ObjectId masing-masing. find() mengembalikan FindCursor; toArray() memuat seluruh hasilnya ke memori sebagai array (cara lain mengonsumsi cursor dibahas di Chapter Cursor dan Pagination). findOne() di sini memakai opsi projection untuk memangkas field — pembahasan lengkapnya di Chapter Query dan Projection. Operator $set dan $inc pada updateOne/updateMany berperilaku sama persis dengan yang kita praktikkan di Section CRUD Operations lewat mongosh.

Perhatikan pula jalannya updateMany({ genre: "fiksi" }, ...): filter itu cocok dengan tiga judul (termasuk Rumah Kaca yang baru ditambahkan), dan deleteMany({ stok: { $lt: 10 } }) hanya menjangkau Bumi Manusia yang stoknya tersisa 7 setelah dikurangi — angka-angka pada output adalah konsekuensi operasi sebelumnya, bukan nilai acak.

_id Bukan String: Bekerja dengan ObjectId #

Driver otomatis mengisi _id untuk setiap document baru — nilainya object ObjectId dari paket bson yang ikut terpasang bersama driver. Tipenya bukan string, dan pembedaan ini terasa begitu _id berpindah media: disimpan di file, dikirim lewat HTTP, atau dicetak ke log semuanya menjadikannya string. File berikut membuktikan akibatnya saat string dipakai langsung sebagai filter:

js
// id-bukan-string.js — _id bertipe ObjectId; string harus dikonversi saat dipakai query
// Jalankan: MONGODB_URI="mongodb://127.0.0.1:27017/?directConnection=true" node id-bukan-string.js
const { MongoClient, ObjectId } = require("mongodb")

const uri = process.env.MONGODB_URI ?? "mongodb://127.0.0.1:27017/?directConnection=true"

async function main() {
  const client = new MongoClient(uri)
  await client.connect()
  const buku = client.db("latihan_js").collection("buku")
  await buku.drop().catch(() => {})

  const hasil = await buku.insertOne({ judul: "Filosofi Teras", harga: 90000 })
  const id = hasil.insertedId
  const idString = id.toString()

  console.log("constructor insertedId :", id.constructor.name)
  console.log("id.toString()          :", idString)

  // Query memakai string mentah — tipe tidak cocok dengan ObjectId
  const pakaiString = await buku.findOne({ _id: idString }, { projection: { judul: 1, _id: 0 } })
  console.log("findOne(_id: string)   :", pakaiString)

  // Konversi string -> ObjectId sebelum dipakai sebagai filter
  const pakaiObjectId = await buku.findOne({ _id: new ObjectId(idString) }, { projection: { judul: 1, _id: 0 } })
  console.log("findOne(new ObjectId)  :", pakaiObjectId)

  // Waktu pembuatan ikut tersimpan di dalam ObjectId
  console.log("getTimestamp()         :", id.getTimestamp().toISOString())

  await client.db("latihan_js").dropDatabase() // bersihkan latihan
  await client.close()
}

main().catch(err => {
  console.error("GAGAL:", err.name, "-", err.message)
  process.exit(1)
})
bash
MONGODB_URI="mongodb://127.0.0.1:27017/?directConnection=true" node id-bukan-string.js

Output yang Dihasilkan:

text
constructor insertedId : ObjectId
id.toString()          : 6ad3ef03394559f9a781fe17
findOne(_id: string)   : null
findOne(new ObjectId)  : { judul: 'Filosofi Teras' }
getTimestamp()         : 2026-10-17T21:56:19.000Z

findOne({ _id: idString }) mengembalikan null meskipun document itu ada — tidak ada pesan galat, karena string memang pola query yang sah; hanya saja tidak ada document _id bertipe string. Konversi new ObjectId(idString) memulihkan tipe aslinya dan query langsung menemukan document. Method getTimestamp() memperlihatkan data yang ikut terkandung di ObjectId: waktu pembuatannya.

Rangkuman #

  • Semua operasi CRUD adalah method pada handle collection: insertOne/insertMany, find/findOne, updateOne/updateMany, deleteOne/deleteMany.
  • Object hasil memuat acknowledged plus field spesifik: insertedId vs insertedIds, matchedCount/modifiedCount, deletedCount.
  • find() mengembalikan FindCursor — toArray() memuat seluruh hasil ke memori sekaligus.
  • _id dari driver bertipe ObjectId; ubah string kembali dengan new ObjectId(...) sebelum dipakai sebagai filter, dan baca waktu pembuatannya lewat getTimestamp().