Validator pada Chapter sebelumnya sudah bisa menolak document tanpa harga. Namun kontrak data sungguhan butuh lebih dari itu: panjang nama, format SKU, rentang harga, hingga daftar kategori sah. Semua itu ditulis dengan operator $jsonSchema — dialek MongoDB dari JSON Schema, standar publik yang juga dipakai di luar dunia database. Setelah menguasai Chapter ini, keterampilan yang kita dapat dapat dibawa ke tool lain yang berbicara bahasa yang sama.

Bahasa $jsonSchema #

Sebuah skema $jsonSchema adalah document yang mendeskripsikan bentuk document lain. Kata kunci yang kita pakai sepanjang Section ini:

Kata kunciBerlaku padaTugas
bsonTypesemua nilaiTipe BSON yang diizinkan
requiredobjectField yang wajib hadir
propertiesobjectAturan per field
minimum / maximumangkaBatas nilai terkecil/terbesar (inklusif)
multipleOfangkaNilai harus kelipatan angka tertentu
minLength / maxLengthstringBatas jumlah karakter
patternstringRegex yang harus cocok
enumsemuaDaftar nilai sah
title / descriptionsemuaMetadata; ikut tampil di pesan galat

Kita pasang kontrak lengkap untuk collection produk toko online:

js
use latihan_validasi
db.produk.drop()
db.createCollection("produk", { validator: { $jsonSchema: {
  bsonType: "object",
  title: "Produk",
  description: "Produk katalog toko online",
  required: ["nama", "sku", "harga", "kategori"],
  properties: {
    nama: { bsonType: "string", minLength: 3, maxLength: 60 },
    sku: { bsonType: "string", pattern: "^[A-Z]{3}-[0-9]{3}$" },
    harga: { bsonType: "number", minimum: 0, maximum: 100000000, multipleOf: 500 },
    kategori: { enum: ["Minuman", "Makanan", "Alat Tulis"] },
    stok: { bsonType: "int", minimum: 0 }
  }
} } })
db.produk.insertOne({ nama: "Kopi Gayo 250g", sku: "MIN-001", harga: 85000, kategori: "Minuman", stok: 40 })

Output yang Dihasilkan:

text
switched to db latihan_validasi
true
{ ok: 1 }
{
  acknowledged: true,
  insertedId: ObjectId('6ad38fb70a61dae06594c843')
}

Bedah skema di atas baris-per-baris:

  1. bsonType: "object" di root — setiap document MongoDB memang object; menuliskannya memastikan validator menolak nilai non-object.
  2. required — empat field yang wajib hadir; stok sengaja tidak masuk daftar karena produk baru boleh belum punya stok.
  3. properties — satu blok aturan per field; field yang tidak disebut (misalnya deskripsi) bebas diisi apa pun.
  4. title dan description — murni metadata, tetapi ikut tercetak pada pesan galat sehingga pesan error jadi lebih mudah dipahami tim.

Batas Angka #

Tiga kata kunci angka bekerja seperti namanya. minimum dan maximum bersifat inklusif — nilai yang sama persis dengan batas tetap lolos. multipleOf memaksa nilai berupa kelipatan; untuk harga dalam rupiah, kelipatan 500 mengikuti pecahan uang yang beredar. Dua percobaan berikut melanggar keduanya:

js
db.produk.insertOne({ nama: "Gula Pasir 1kg", sku: "MAK-001", harga: -5000, kategori: "Makanan" })
db.produk.insertOne({ nama: "Teh Melati 100g", sku: "MIN-002", harga: 15100, kategori: "Minuman" })

Output yang Dihasilkan:

text
Uncaught:

MongoServerError: Document failed validation
Additional information: {
  failingDocumentId: ObjectId('6ad38fb70a61dae06594c844'),
  details: {
    operatorName: '$jsonSchema',
    title: 'Produk',
    description: 'Produk katalog toko online',
    schemaRulesNotSatisfied: [
      {
        operatorName: 'properties',
        propertiesNotSatisfied: [
          {
            propertyName: 'harga',
            details: [
              {
                operatorName: 'minimum',
                specifiedAs: { minimum: 0 },
                reason: 'comparison failed',
                consideredValue: -5000
              }
            ]
          }
        ]
      }
    ]
  }
}
Uncaught:

MongoServerError: Document failed validation
Additional information: {
  failingDocumentId: ObjectId('6ad38fb70a61dae06594c845'),
  details: {
    operatorName: '$jsonSchema',
    title: 'Produk',
    description: 'Produk katalog toko online',
    schemaRulesNotSatisfied: [
      {
        operatorName: 'properties',
        propertiesNotSatisfied: [
          {
            propertyName: 'harga',
            details: [
              {
                operatorName: 'multipleOf',
                specifiedAs: { multipleOf: 500 },
                reason: 'considered value is not a multiple of the specified value',
                consideredValue: 15100
              }
            ]
          }
        ]
      }
    ]
  }
}

Dua galat muncul berurutan: yang pertama melanggar minimum: 0 dengan consideredValue: -5000, yang kedua melanggar multipleOf: 500 — 15100 tidak habis dibagi 500. JSON Schema juga menyediakan exclusiveMinimum dan exclusiveMaximum bila batasnya tidak boleh ikut lolos.

Aturan String #

minLength dan maxLength menghitung jumlah karakter, sedangkan pattern menuntut string cocok dengan regular expression. Pola ^[A-Z]{3}-[0-9]{3}$ pada sku membaca: tiga huruf kapital, tanda hubung, tiga digit — format seperti MIN-001. Kita uji dengan SKU huruf kecil dan nama yang terlalu pendek:

js
db.produk.insertOne({ nama: "Kopi Tubruk 200g", sku: "min-004", harga: 12000, kategori: "Minuman" })
db.produk.insertOne({ nama: "AB", sku: "MIN-004", harga: 12000, kategori: "Minuman" })

Output yang Dihasilkan:

text
Uncaught:

MongoServerError: Document failed validation
Additional information: {
  failingDocumentId: ObjectId('6ad38fb70a61dae06594c846'),
  details: {
    operatorName: '$jsonSchema',
    title: 'Produk',
    description: 'Produk katalog toko online',
    schemaRulesNotSatisfied: [
      {
        operatorName: 'properties',
        propertiesNotSatisfied: [
          {
            propertyName: 'sku',
            details: [
              {
                operatorName: 'pattern',
                specifiedAs: { pattern: '^[A-Z]{3}-[0-9]{3}$' },
                reason: 'regular expression did not match',
                consideredValue: 'min-004'
              }
            ]
          }
        ]
      }
    ]
  }
}
Uncaught:

MongoServerError: Document failed validation
Additional information: {
  failingDocumentId: ObjectId('6ad38fb70a61dae06594c847'),
  details: {
    operatorName: '$jsonSchema',
    title: 'Produk',
    description: 'Produk katalog toko online',
    schemaRulesNotSatisfied: [
      {
        operatorName: 'properties',
        propertiesNotSatisfied: [
          {
            propertyName: 'nama',
            details: [
              {
                operatorName: 'minLength',
                specifiedAs: { minLength: 3 },
                reason: 'specified string length was not satisfied',
                consideredValue: 'AB'
              }
            ]
          }
        ]
      }
    ]
  }
}

Daftar Nilai Sah dengan enum #

enum membatasi nilai pada daftar tertutup — pas untuk kategori, status, atau satuan. Nilai di luar daftar ditolak walaupun tipenya benar:

js
db.produk.insertOne({ nama: "Roti Tawar", sku: "MAK-002", harga: 17500, kategori: "Snack" })

Output yang Dihasilkan:

text
Uncaught:

MongoServerError: Document failed validation
Additional information: {
  failingDocumentId: ObjectId('6ad38fb70a61dae06594c848'),
  details: {
    operatorName: '$jsonSchema',
    title: 'Produk',
    description: 'Produk katalog toko online',
    schemaRulesNotSatisfied: [
      {
        operatorName: 'properties',
        propertiesNotSatisfied: [
          {
            propertyName: 'kategori',
            details: [
              {
                operatorName: 'enum',
                specifiedAs: { enum: [ 'Minuman', 'Makanan', 'Alat Tulis' ] },
                reason: 'value was not found in enum',
                consideredValue: 'Snack'
              }
            ]
          }
        ]
      }
    ]
  }
}

Perhatikan struktur galatnya: operatorName: 'enum' lengkap dengan daftar nilai sah di specifiedAs — pembaca pesan galat langsung tahu nilai mana saja yang diterima.

Sebelum menutup, bersihkan database latihan:

js
db.dropDatabase()

Output yang Dihasilkan:

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

Rangkuman #

  • $jsonSchema mengikuti standar publik JSON Schema: properties untuk aturan per field, required untuk kehadiran, bsonType untuk tipe.
  • Kata kunci angka: minimum, maximum (inklusif), multipleOf; tambahkan exclusiveMinimum/exclusiveMaximum bila batas tidak boleh lolos.
  • Kata kunci string: minLength, maxLength, pattern (regex).
  • enum mengunci nilai pada daftar tertutup dan pesan galatnya menampilkan daftar nilai sah.
  • title dan description hanyalah metadata, tetapi membuat pesan galat jauh lebih ramah pembaca.

Satu hal yang belum kita bedah dalam: daftar tipe apa saja yang diterima bsonType, dan apa bedanya "number" dengan "int". Itu fokus Chapter berikutnya.