Di driver murni, collection apa pun bisa menerima document berbentuk apa pun — kebebasan yang berubah menjadi beban begitu dua fitur ditulis oleh dua orang pada dua hari berbeda. Schema Mongoose memutus masalah itu di sumbernya: satu deklarasi menentukan field apa yang dikenal, bertipe apa, dan apa nilai cadangannya. Bab ini merakit schema untuk entitas buku, mengubahnya menjadi model, lalu memeriksa apa yang benar-benar tersimpan di collection — termasuk tiga field yang tidak pernah kita tulis: _id, createdAt, dan updatedAt.
Skema Menentukan Bentuk Document #
Mongoose menyediakan SchemaType untuk semua tipe BSON yang lazim. Ringkasannya:
| SchemaType | Untuk | Catatan |
|---|---|---|
String, Number, Boolean | skalar dasar | dilengkapi casting otomatis |
Date | tanggal dan waktu | disimpan sebagai BSON Date |
mongoose.Schema.Types.ObjectId | referensi antar document | bahasan bab Populate dan References |
[String], [Number] | array satu tipe | [{}] untuk array subdocument |
Map | pasangan kunci-nilai dinamis | alternatif object polos |
Kita rakit schema buku lengkap dengan opsi collection yang mengunci nama collection — tanpa opsi ini, model Buku akan otomatis memakai collection bukus:
// skema-buku.js — schema lengkap, model, dan insert pertama
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_skema" })
await mongoose.connection.db.dropDatabase()
const bukuSchema = new mongoose.Schema(
{
kode: { type: String, required: true, unique: true },
judul: { type: String, required: true },
penulis: { type: [String], default: [] },
harga: { type: Number, required: true, min: 0 },
tersedia: { type: Boolean, default: true },
terbit: { type: Date },
stokDiperbarui: { type: Date, default: () => new Date() },
penerbit: { type: mongoose.Schema.Types.ObjectId },
},
{ timestamps: true, collection: "buku" }
)
const Buku = mongoose.model("Buku", bukuSchema)
const buku = new Buku({
kode: "BK-001",
judul: "Laskar Pelangi",
penulis: ["Andrea Hirata"],
harga: 95000,
terbit: new Date("2005-07-01"),
rating: 5, // sengaja: field tak dikenal schema
})
console.log("1. _id sebelum save:", buku._id.constructor.name, "| isNew:", buku.isNew)
console.log("2. field tak dikenal dibuang:", buku.rating)
await Buku.init() // tunggu index (misalnya kode yang unik) selesai dibangun
const tersimpan = await buku.save()
console.log("3. createdAt:", tersimpan.createdAt.toISOString())
console.log("4. updatedAt bertipe Date:", tersimpan.updatedAt instanceof Date)
const mentah = await mongoose.connection.db.collection("buku").findOne({ kode: "BK-001" })
console.log("5. field tersimpan:", Object.keys(mentah).join(", "))
const indeks = await mongoose.connection.db.collection("buku").listIndexes().toArray()
console.log("6. index:", indeks.map((i) => `${i.name}${i.unique ? " (unique)" : ""}`).join(", "))
await mongoose.disconnect()
}
main().catch((err) => {
console.error("GAGAL:", err.name, "-", err.message)
process.exit(1)
})cd /home/z/my-project/tutorial
MONGODB_URI="mongodb://127.0.0.1:27017/?directConnection=true" node content/mongodb/mongodb-dengan-nodejs/schemas-dan-models-mongoose/skema-buku.jsOutput yang Dihasilkan:
1. _id sebelum save: ObjectId | isNew: true
2. field tak dikenal dibuang: undefined
3. createdAt: 2026-10-17T22:15:22.482Z
4. updatedAt bertipe Date: true
5. field tersimpan: _id, kode, judul, penulis, harga, tersedia, terbit, stokDiperbarui, createdAt, updatedAt, __v
6. index: _id_, kode_1 (unique)Mari kita bedah hasilnya baris per baris:
- Baris 1 —
buku._idsudah berupaObjectIdbahkan sebelumsave(): Mongoose membuat_iddi sisi klien saat document dibangun, bukan menunggu server. FlagisNew: truemenandai document yang belum pernah disimpan. - Baris 2 —
rating: 5tidak ada di schema, jadi langsung dibuang saat pembuatan document (perilakustrictbawaan). Schema bukan sekadar saran; ia filter aktif. - Baris 3-4 — opsi
{ timestamps: true }menambahkancreatedAtdanupdatedAtotomatis pada setiap save, tanpa perlu kita menulis field tersebut. - Baris 5 — isi collection memuat semua field schema plus tiga tambahan:
_id,createdAt,updatedAt, dan__v(versionKey internal Mongoose).tersediaikut tersimpan dengan nilaitruedaridefault, danstokDiperbaruiterisi karenadefaultberbentuk fungsi() => new Date()— dievaluasi saat document dibuat, bukan saat schema didefinisikan. - Baris 6 — collection memiliki dua index:
_id_bawaan MongoDB dankode_1dengan tandaunique.
Tiga Nilai Otomatis yang Dikelola Mongoose #
Schema di atas memperlihatkan tiga mekanisme nilai otomatis yang berbeda sumbernya. Pertama, _id: dibangkitkan klien sebagai ObjectId dan dijamin unik lewat index _id_. Kedua, default: nilai statis (true, []) atau fungsi (() => new Date()) yang mengisi field yang tidak dikirim pemanggil. Ketiga, timestamps: opsi level schema yang mengisi createdAt sekali saat insert dan memperbarui updatedAt pada setiap operasi tulis berikutnya.
Ketiganya bekerja pada jalur document (save(), create()). Operasi update langsung seperti updateOne() melewati sebagian besar mekanisme ini — perilaku yang memengaruhi pilihan desain dan kita bahas lebih dalam di bab Middleware Mongoose.
Menata Model di Dalam Aplikasi #
Kembali ke struktur aplikasi bab pertama: tempat schema dan model adalah direktori src/models/, satu berkas per entitas. Berkas model mengekspor hasil mongoose.model() langsung sehingga lapisan service cukup meng-require dan memakainya:
// mdtest: skip — pola penataan berkas, bukan contoh yang dijalankan
const mongoose = require("mongoose")
const bukuSchema = new mongoose.Schema(
{ /* ...definisi field seperti di atas... */ },
{ timestamps: true, collection: "buku" }
)
module.exports = mongoose.model("Buku", bukuSchema)Dua konvensi menjaga kode tetap sehat ketika model berjumlah banyak. Pertama, nama model ("Buku") harus unik per koneksi — Mongoose menyimpan model dalam registry, dan mendaftarkan nama sama dua kali dengan schema berbeda melempar galat OverwriteModelError; cukup require berkas yang sama alih-alih memanggil mongoose.model() ulang. Kedua, karena schema adalah satu-satunya tempat bentuk document didefinisikan, perubahan bentuk data selalu berawal dari berkas model — service tidak pernah menambahkan field baru diam-diam.
unique Adalah Index, Bukan Validasi #
Baris terakhir output di atas adalah pelajaran yang sering disalahpahami: unique: true pada schema menghasilkan index unik di server, bukan pemeriksaan di sisi aplikasi. Konsekuensinya besar. Index unik dijamin server pada saat insert — dua permintaan bersamaan dengan kode sama tetap akan ditolak satu di antaranya, sesuatu yang pemeriksaan “cari dulu, baru insert” di aplikasi tidak bisa janjikan. Sebaliknya, penolakan itu datang sebagai galat E11000 dari server, bukan sebagai ValidationError Mongoose.
Rangkuman #
- Schema mendeklarasikan field, tipe, dan opsi; field di luar schema dibuang oleh mode
strict, dan model dibuat denganmongoose.model("Nama", skema, opsional: namaCollection). _iddibangkitkan klien sebagaiObjectId,defaultbisa berupa nilai statis atau fungsi, dan{ timestamps: true }mengelolacreatedAt/updatedAt.unique: truemembuat index unik di server — penegakan terjamin, tetapi kegagalannya datang sebagai galatE11000, bukanValidationError.Buku.init()menunggu index selesai dibangun sebelum operasi berikutnya bergantung padanya.