Driver murni memberi kita kendali penuh, tetapi juga memindahkan semua disiplin ke tangan kita: tidak ada yang melarang satu document memakai field tahun bertipe string sementara document lain memakai angka. Mongoose adalah ODM (Object Data Mapper) untuk MongoDB yang berdiri di atas driver resmi — ia menambahkan lapisan schema, validasi, dan middleware tanpa mengganti cara kerja server. Bab ini menghubungkan Mongoose pertama kali, membedah apa yang diberikannya, dan yang sama pentingnya: kapan lapisan itu justru tidak diperlukan.

Koneksi dan Model Pertama #

Pasang Mongoose seperti biasa (npm install mongoose) — di lingkungan latihan kita paket 9.x sudah tersedia. Satu panggilan mongoose.connect() menggantikan seluruh modul db.js dari bab driver: koneksi disimpan global di mongoose.connection dan otomatis dibagikan ke semua model:

js
// hello-mongoose.js — koneksi dan model pertama dengan Mongoose
const mongoose = require("mongoose")

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

async function main() {
  await mongoose.connect(uri, { dbName: "latihan_node_mongoose" })
  console.log("1. terhubung, database:", mongoose.connection.name)

  // mulai dari keadaan bersih agar contoh dapat diulang
  await mongoose.connection.db.dropDatabase()

  const bukuSchema = new mongoose.Schema({
    judul: { type: String, required: true },
    penulis: String,
    tahun: Number,
    harga: { type: Number, default: 0 },
  })

  const Buku = mongoose.model("Buku", bukuSchema) // collection "bukus"

  const buku = await Buku.create({
    judul: "Laskar Pelangi",
    penulis: "Andrea Hirata",
    tahun: "2005", // sengaja string: Mongoose akan meng-casting
    harga: 95000,
  })
  console.log("2. tersimpan, _id bertipe:", buku._id.constructor.name)
  console.log("3. tahun setelah casting:", buku.tahun, "| typeof:", typeof buku.tahun)

  const polos = await Buku.findOne({ judul: "Laskar Pelangi" }).lean()
  console.log("4. hasil findOne().lean():", JSON.stringify(polos))

  console.log("5. collection yang terpakai:", Object.keys(mongoose.connection.collections))

  await mongoose.disconnect()
  console.log("6. koneksi ditutup")
}

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/menggunakan-mongoose/hello-mongoose.js

Output yang Dihasilkan:

text
1. terhubung, database: latihan_node_mongoose
2. tersimpan, _id bertipe: ObjectId
3. tahun setelah casting: 2005 | typeof: number
4. hasil findOne().lean(): {"_id":"6ad3f37a034336ef958c7f27","judul":"Laskar Pelangi","penulis":"Andrea Hirata","tahun":2005,"harga":95000,"__v":0}
5. collection yang terpakai: [ 'bukus' ]
6. koneksi ditutup

Empat peristiwa pada output itu adalah kartu nama Mongoose. mongoose.connect(uri, { dbName }) memilih database lewat opsi, bukan lewat path URI. mongoose.model("Buku", skema) mengubah nama model Buku menjadi collection bukus — pluralisasi otomatis yang bisa diganti dengan argumen nama collection eksplisit. Nilai tahun yang kita kirim sebagai string "2005" keluar sebagai angka 2005 karena SchemaType melakukan casting sebelum menyimpan. Terakhir, __v: 0 adalah versionKey internal untuk optimistik concurrency; .lean() mengembalikan objek JavaScript polos tanpa method document, pilihan yang tepat saat kita hanya ingin membaca.

Kita bedah bagian-bagian kuncinya:

  1. new mongoose.Schema({...}) — peta field ke tipe. judul wajib diisi (required: true), penulis polos bertipe String, dan harga punya nilai cadangan 0 bila tidak dikirim.
  2. mongoose.model("Buku", bukuSchema) — mendaftarkan constructor model; dari sini Buku.create(), Buku.find(), dan operasi lain tersedia.
  3. Buku.create({...}) — membuat sekaligus menyimpan satu document, melewati casting dan validasi schema lebih dulu.
  4. mongoose.connection.db.dropDatabase() — bukti bahwa objek Db milik driver asli tetap terjangkau dari dalam Mongoose.
  5. mongoose.disconnect() — menutup koneksi global; padanan client.close() pada driver.

Apa yang Diberikan ODM #

Mongoose menjawab tiga kebutuhan yang sebelumnya harus kita bangun sendiri di atas driver:

KebutuhanDi driver murniDi Mongoose
Bentuk documentkonvensi tim, tanpa penegakanSchema mendefinisikan field dan tipe
Integritas datajsonSchema di server, atau tidak sama sekalivalidasi required, enum, min/max sebelum operasi
Logika lintas-operasiditulis manual di setiap fungsimiddleware pre/post per event
Relasi antar collection$lookup manualref + populate()

Skema juga menjadi dokumentasi yang hidup: membaca bukuSchema langsung memberi tahu field apa saja yang diharapkan beserta tipenya, tanpa perlu membuka contoh document di database. Posisi Mongoose dalam arsitektur juga perlu dipahami sejak awal: ia tidak menggantikan driver, melainkan membungkusnya. Setiap operasi Buku.find() pada akhirnya menjadi operasi find pada driver yang sama, melalui connection pool yang sama — sehingga seluruh pemahaman kita tentang connection string, pool, dan timeout dari Section Integrasi Aplikasi tetap berlaku sepenuhnya.

Kapan Tidak Perlu Mongoose #

Lapisan tambahan selalu membawa biaya, dan ada situasi ketika Mongoose justru menghambat. Pertama, pekerjaan ETL dan migrasi data berskala besar: insertMany ratusan ribu document lebih cepat lewat driver karena tidak melewati casting dan pelacakan per document (kita ukur angkanya di bab terakhir section ini). Kedua, skrip administratif sekali jalan yang menyalin atau memeriksa data — tidak ada domain model yang perlu dijaga. Ketiga, aplikasi yang didominasi aggregation pipeline kompleks: pipeline tetap ditulis sama, sementara hasilnya biasanya dibaca dengan .lean() sehingga mayoritas fitur Mongoose tidak terpakai. Keempat, tim yang sudah memiliki lapisan validasi dan repository matang — menambah ODM berarti dua sumber kebenaran untuk bentuk data.

Sisa section ini membedah Mongoose lapis demi lapis: schema dan model (bab berikutnya), validasi, middleware siklus hidup, relasi antar collection lewat populate, aggregation, dan transaksi. Semua contoh memakai koneksi global yang sama, jadi pola connect() di awal dan disconnect() di akhir file akan berulang — itulah praktik yang nanti kita bawa ke aplikasi sungguhan.

Rangkuman #

  • mongoose.connect() menyimpan satu koneksi global; mongoose.model() mengikat schema ke collection (nama dijamak secara otomatis).
  • Schema memberi casting tipe ("2005" menjadi 2005), validasi, dan dokumentasi bentuk document; _id dan __v dikelola otomatis.
  • .lean() mengembalikan objek polos untuk pembacaan cepat tanpa fitur document.
  • Mongoose layak dipakai saat bentuk data dan integritasnya penting; lewati untuk ETL berskala besar, skrip administratif, atau aplikasi yang didominasi aggregation.