Setiap proyek Node.js + MongoDB dimulai dari satu keputusan: memakai driver resmi apa adanya, atau menambah Mongoose di atasnya. Keduanya berbicara ke server yang sama lewat protokol yang sama, jadi pertanyaannya bukan mana yang “lebih benar”, melainkan fitur mana yang kita butuhkan dan biaya apa yang rela dibayar. Section ini telah memakai keduanya secara bergantian; bab penutup ini merangkum perbedaannya dalam satu tabel, mengukur biayanya dengan angka nyata, lalu menutup dengan pola hibrida yang memanfaatkan keduanya sekaligus.

Peta Kekuatan Kedua Pustaka #

AspekDriver mongodbMongoose
Bentuk databebas; disiplin di tangan timschema eksplisit dengan casting tipe
Validasimanual di lapisan aplikasirequired, enum, min/max, custom, async
Logika per-operasiditulis di setiap fungsimiddleware pre/post per event
Relasi antar collection$lookup manualref + populate()
TransaksiwithTransaction + sessionconnection.transaction() + integrasi change tracking
Kendali querypenuh, satu-ke-satu dengan serverquery builder + document API (bisa lean() untuk polos)
Operasi batch besarpaling ringan — tanpa validasi/castingmembayar validasi per document
Kurva belajarkecil jika sudah paham MongoDBtambahan konsep schema, middleware, document state

Baris “kendali query” dan “batch besar” adalah kekuatan driver; baris schema hingga populate adalah alasan Mongoose ada. Perhatikan bahwa kolom kanan sebagian besar bisa ditiru di kolom kiri — hanya saja kita menulisnya sendiri, mengujinya sendiri, dan menjaganya sendiri di setiap fitur baru.

Pengukuran Kecil yang Jujur #

Angka lebih membantu daripada opini. File berikut menjalankan tugas yang sama tiga jalur: driver murni, Mongoose penuh, dan hibrida — masing-masing rata-rata tiga putaran menulis dan membaca 200 document:

js
// banding-driver-mongoose.js — pengukuran driver vs Mongoose (rata-rata 3 putaran)
const { MongoClient } = require("mongodb")
const mongoose = require("mongoose")

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

function buatData(n) {
  return Array.from({ length: n }, (_, i) => ({
    kode: `BK-${String(i).padStart(4, "0")}`,
    judul: `Buku Nomor ${i}`,
    harga: 10000 + i,
    stok: i % 10,
  }))
}

// jalankan fn beberapa kali, kembalikan rata-rata ms
async function ukur(fn, kali = PUTARAN) {
  const catatan = []
  for (let i = 0; i < kali; i++) {
    const mulai = performance.now()
    await fn()
    catatan.push(performance.now() - mulai)
  }
  return catatan.reduce((a, b) => a + b, 0) / kali
}

async function main() {
  // ---- jalur 1: driver murni ----
  const client = new MongoClient(uri)
  await client.connect()
  const db = client.db("latihan_node_banding")
  await db.dropDatabase()

  const tulisDriver = await ukur(async () => {
    await db.collection("buku").deleteMany({})
    await db.collection("buku").insertMany(buatData(JUMLAH))
  })
  const bacaDriver = await ukur(() => db.collection("buku").find({}).toArray())
  console.log(`driver   : tulis ${JUMLAH} doc ~${tulisDriver.toFixed(0)} ms | baca ~${bacaDriver.toFixed(0)} ms`)
  await client.close()

  // ---- jalur 2: Mongoose ----
  await mongoose.connect(uri, { dbName: "latihan_node_banding" })
  await mongoose.connection.db.dropDatabase()

  const bukuSchema = new mongoose.Schema({
    kode: String,
    judul: String,
    harga: Number,
    stok: Number,
  })
  const Buku = mongoose.model("Buku", bukuSchema, "buku")

  const tulisMongoose = await ukur(async () => {
    await Buku.deleteMany({})
    await Buku.insertMany(buatData(JUMLAH)) // lolos validasi + casting per document
  })
  const bacaLean = await ukur(() => Buku.find({}).lean())
  const bacaDoc = await ukur(() => Buku.find({}))

  console.log(`mongoose : tulis ${JUMLAH} doc ~${tulisMongoose.toFixed(0)} ms (dengan validasi + casting)`)
  console.log(`mongoose : baca lean ~${bacaLean.toFixed(0)} ms | sebagai document ~${bacaDoc.toFixed(0)} ms`)

  // ---- jalur 3: hibrida — koneksi Mongoose, collection mentah driver ----
  const tulisHibrida = await ukur(async () => {
    await Buku.deleteMany({})
    const koleksi = mongoose.connection.db.collection("buku")
    await koleksi.insertMany(buatData(JUMLAH)) // tanpa validasi, lewat pool yang sama
  })
  console.log(`hibrida  : tulis ${JUMLAH} doc ~${tulisHibrida.toFixed(0)} ms (collection mentah lewat koneksi Mongoose)`)

  const [contohLean, contohDoc] = [await Buku.findOne({}).lean(), await Buku.findOne({})]
  console.log("tipe hasil baca:", contohLean.constructor.name, "vs", contohDoc.constructor.name)

  await mongoose.disconnect()
}

main().catch((err) => {
  console.error("GAGAL:", err.name, "-", err.message)
  process.exit(1)
})
bash
cd /home/z/my-project/tutorial
MONGODB_URI="mongodb://127.0.0.1:27017/?directConnection=true" node content/mongodb/mongodb-dengan-nodejs/driver-vs-mongoose/banding-driver-mongoose.js

Output yang Dihasilkan:

text
driver   : tulis 200 doc ~8 ms | baca ~4 ms
mongoose : tulis 200 doc ~12 ms (dengan validasi + casting)
mongoose : baca lean ~3 ms | sebagai document ~4 ms
hibrida  : tulis 200 doc ~5 ms (collection mentah lewat koneksi Mongoose)
tipe hasil baca: Object vs model

Cara membacanya: penulisan lewat Mongoose sekitar setengah kali lebih lambat daripada driver pada volume ini — biaya validasi dan casting per document yang kita lihat bekerja di bab-bab sebelumnya. Pembacaan ketiganya berdekatan; pada 200 document, waktu tempuh didominasi jaringan dan server, bukan lapisan pustaka. Baris terakhir menegaskan perbedaan hasil: lean() mengembalikan Object polos, query biasa mengembalikan instance model dengan method dan pelacakan perubahan. Angka-angka ini dari satu mesin dan satu putaran kecil — perlakukan sebagai petunjuk arah, bukan tolok ukur mutlak; pada volume jutaan document, selisih penulisan inilah yang berlipat.

Kapan Memilih Masing-Masing #

Pilih driver murni ketika: aplikasi adalah skrip ETL/migrasi/administratif, bentuk data dinamis atau baru diketahui saat berjalan, tim sudah punya lapisan validasi dan repository matang, atau beban tulis batch besar adalah jantung aplikasi. Pilih Mongoose ketika: aplikasi memiliki domain model yang jelas, integritas data penting sejak awal, tim besar membutuhkan schema sebagai kontrak bersama, atau fitur populate dan middleware memang menyelamatkan kode berulang.

Pendekatan Hibrida #

Kabar baiknya: pilihan ini tidak permanen dan tidak eksklusif. mongoose.connection.db adalah objek Db driver asli — jalur ketiga pada pengukuran di atas memakainya untuk menulis 200 document tanpa validasi, melalui connection pool yang sama dengan model. Pola hibrida yang lazim: model dan validasi Mongoose untuk operasi domain (pesan, pengguna, pembayaran), dan collection() mentah untuk impor data massal, backfill, atau operasi administratif yang sudah tervalidasi di sumbernya.

js Mengambil collection mentah dari koneksi Mongoose
// mdtest: skip — Buku dan koneksi berasal dari file di atas
const koleksi = mongoose.connection.db.collection("buku")
await koleksi.insertMany(dataSumberTervalidasi) // jalur cepat, tanpa validasi ulang

Kesimpulan #

Driver menawarkan kendali penuh dan jalur termurah per operasi; Mongoose menawarkan kontrak data, validasi, dan otomasi yang membuat aplikasi domain tetap sehat saat fitur bertambah. Untuk aplikasi SaaS dan API produk dengan tim yang tumbuh, Mongoose umumnya menghemat lebih banyak waktu daripada biayanya; untuk pipeline data dan pekerjaan operasional, driver murni adalah pilihan yang tepat. Keduanya hidup di atas server MongoDB yang sama — dan karena Mongoose membukakan objek driver-nya, keputusan hari ini tidak menutup pintu besok.

Rangkuman #

  • Tabel perbandingan: driver unggul di kendali penuh dan operasi batch besar; Mongoose unggul di schema, validasi, middleware, dan populate.
  • Pengukuran nyata: penulisan Mongoose membayar validasi/casting (di lingkungan uji kita ~8 ms vs ~12 ms per 200 document); pembacaan pada volume kecil didominasi jaringan.
  • lean() mengembalikan objek polos; query biasa mengembalikan instance model — pilih sesuai kebutuhan hasil.
  • Pola hibrida memakai mongoose.connection.db.collection(...) untuk jalur cepat di atas pool yang sama, dengan catatan schema dan validasinya dilewati.