Chapter sebelumnya menunjukkan bagaimana salah ketik field dan tipe campur masuk ke collection tanpa protes. MongoDB punya jawaban bawaan untuk itu: schema validation — sekumpulan aturan yang menempel pada collection dan membuat server menolak document yang menyimpang. Kita pindahkan sebagian disiplin dari kode aplikasi kembali ke database, tanpa meninggalkan model document.

Peta materi Chapter ini:

  • Di mana validator hidup dan kapan aturannya diperiksa.
  • Membuat collection dengan validator lewat db.createCollection().
  • Menambahkan validator pada collection yang sudah berjalan lewat collMod.
  • Membaca kembali validator aktif dengan db.getCollectionInfos().

Validator Menempel di Level Collection #

Validator disimpan sebagai opsi collection, bukan aturan global database dan bukan pula kode di aplikasi. Saat sebuah insert atau update datang, server memeriksa document hasil akhir terhadap validator sebelum data ditulis — data yang gagal ditolak dengan error DocumentValidationFailure.

Tiga fakta kunci yang perlu diingat:

  • Dibuat bersama collection — opsi validator pada db.createCollection().
  • Bisa dipasang belakangan — perintah collMod mengubah validator collection yang sudah ada isinya.
  • Hanya berlaku ke depan — dokumen lama tidak diperiksa ulang; validasi berjalan saat insert dan update.

MongoDB menerima dua gaya validator: query filter (misalnya { harga: { $type: "number" } }) dan $jsonSchema yang mengikuti standar JSON Schema. Sepanjang Section ini kita memakai $jsonSchema karena lebih ekspresif dan menjadi rekomendasi resmi.

Membuat Collection dengan Validator #

Kita mulai dari collection produk yang lahir langsung dengan kontrak bentuk: nama wajib berupa string, harga wajib angka bulat positif:

js
use latihan_validasi
db.produk.drop()
db.createCollection("produk", { validator: { $jsonSchema: {
  bsonType: "object",
  required: ["nama", "harga"],
  properties: {
    nama: { bsonType: "string" },
    harga: { bsonType: "number", minimum: 0 }
  }
} } })
show collections

Output yang Dihasilkan:

text
switched to db latihan_validasi
true
{ ok: 1 }
produk

Document yang memenuhi kontrak masuk seperti biasa:

js
db.produk.insertOne({ nama: "Kopi Gayo 250g", harga: 85000, kategori: "Minuman", stok: 40 })

Output yang Dihasilkan:

text
{
  acknowledged: true,
  insertedId: ObjectId('6ad38f29a6bab46d89c19498')
}

Sedangkan document dengan harga negatif ditolak mentah-mentah. mongosh menampilkan galatnya apa adanya:

js
db.produk.insertOne({ nama: "Gula Pasir 1kg", harga: -5000 })

Output yang Dihasilkan:

text
Uncaught:

MongoServerError: Document failed validation
Additional information: {
  failingDocumentId: ObjectId('6ad38f29a6bab46d89c19499'),
  details: {
    operatorName: '$jsonSchema',
    schemaRulesNotSatisfied: [
      {
        operatorName: 'properties',
        propertiesNotSatisfied: [
          {
            propertyName: 'harga',
            details: [
              {
                operatorName: 'minimum',
                specifiedAs: { minimum: 0 },
                reason: 'comparison failed',
                consideredValue: -5000
              }
            ]
          }
        ]
      }
    ]
  }
}

Mari kita bedah bagian-bagian galat tersebut:

  1. MongoServerError: Document failed validation — jenis galatnya; kode error-nya DocumentValidationFailure.
  2. failingDocumentId — _id yang akan dipakai bila document sempat dibuat; membantu pelacakan dari log aplikasi.
  3. schemaRulesNotSatisfied — daftar aturan yang dilanggar, lengkap dengan propertyName: 'harga', aturan minimum: 0, dan consideredValue: -5000. Pesan ini dirancang untuk dibaca manusia, jadi manfaatkan.

collMod untuk Collection yang Sudah Berjalan #

Collection yang sudah dipakai produksi tidak bisa dibuang lalu dibuat ulang begitu saja. Perintah collMod mengubah opsi collection yang sudah ada — termasuk menambahkan validator:

js
db.produk.drop()
db.createCollection("produk")
db.produk.insertOne({ nama: "Gula Pasir 1kg", hrga: 15000 })
db.runCommand({
  collMod: "produk",
  validator: { $jsonSchema: {
    bsonType: "object",
    required: ["nama", "harga"],
    properties: {
      nama: { bsonType: "string" },
      harga: { bsonType: "number", minimum: 0 }
    }
  } }
})
db.produk.find()

Output yang Dihasilkan:

text
true
{ ok: 1 }
{
  acknowledged: true,
  insertedId: ObjectId('6ad38f29a6bab46d89c1949a')
}
{
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1792249641, i: 5 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1792249641, i: 5 })
}
[
  {
    _id: ObjectId('6ad38f29a6bab46d89c1949a'),
    nama: 'Gula Pasir 1kg',
    hrga: 15000
  }
]

Hasil collMod berisi ok: 1 sebagai tanda sukses, plus $clusterTime dan operationTime — metadata replikasi dari replica set rs0 tempat server kita berjalan. Perhatikan baris terakhir: document lama yang memakai field hrga tetap ada setelah validator dipasang.

Lalu bagaimana nasib document lama itu? Ia boleh dibaca selamanya, tetapi begitu di-update, hasil akhirnya harus lolos validator. Dua percobaan berikut menunjukkan dua sisi aturan itu:

js
db.produk.updateOne({ nama: "Gula Pasir 1kg" }, { $set: { stok: 5 } })
db.produk.updateOne({ nama: "Gula Pasir 1kg" }, { $set: { harga: 15000 }, $unset: { hrga: "" } })
db.produk.find()

Output yang Dihasilkan:

text
Uncaught:

MongoServerError: Plan executor error during update :: caused by :: Document failed validation
Additional information: {
  failingDocumentId: ObjectId('6ad38f29a6bab46d89c1949a'),
  details: {
    operatorName: '$jsonSchema',
    schemaRulesNotSatisfied: [
      {
        operatorName: 'required',
        specifiedAs: { required: [ 'nama', 'harga' ] },
        missingProperties: [ 'harga' ]
      }
    ]
  }
}
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
[
  {
    _id: ObjectId('6ad38f29a6bab46d89c1949a'),
    nama: 'Gula Pasir 1kg',
    harga: 15000
  }
]

Update pertama gagal karena document lama belum punya harga — mengubah field lain tidak menyembuhkannya. Update kedua memperbaiki akar masalahnya (memasang harga, membuang hrga), dan setelah valid, document pun bisa diedit bebas. Perhatikan pula bentuk galatnya: Plan executor error during update :: caused by :: Document failed validation — isi detailnya sama dengan galat insert, hanya pembungkusnya berbeda.

Melihat Validator Aktif #

Validator tersimpan di metadata collection, sehingga bisa dibaca kapan saja — berguna saat menganalisis collection milik tim lain:

js
db.getCollectionInfos({ name: "produk" })[0].options.validator

Output yang Dihasilkan:

text
{
  '$jsonSchema': {
    bsonType: 'object',
    required: [ 'nama', 'harga' ],
    properties: {
      nama: { bsonType: 'string' },
      harga: { bsonType: 'number', minimum: 0 }
    }
  }
}

db.getCollectionInfos() mengembalikan array berisi seluruh collection yang cocok dengan filter { name: "produk" }; indeks [0] mengambil satu-satunya hasil, lalu .options.validator memotong tepat ke validatornya. Bersihkan database latihan:

js
db.dropDatabase()

Output yang Dihasilkan:

text
{ ok: 1, dropped: 'latihan_validasi' }

Rangkuman #

  • Validator adalah opsi collection; server memeriksa setiap insert dan update terhadapnya sebelum menulis.
  • Pasang validator saat db.createCollection(), atau belakangan lewat collMod — collection berjalan tidak perlu dibuang.
  • Dokumen yang gagal validasi ditolak dengan error DocumentValidationFailure yang detailnya bisa dibaca manusia.
  • Validator tidak retroaktif: data lama tetap tersimpan, tetapi update atasnya wajib lolos aturan.
  • db.getCollectionInfos() membaca kembali validator yang tersimpan di metadata collection.

Chapter berikutnya membedah bahasa aturan yang kita pakai di sini: operator $jsonSchema beserta kata kuncinya.