Validasi Input — API

Jangan pernah percaya input dari client. Selalu validasi sebelum memproses. Validasi wajib: app.post("/users", (req, res) => { const { name, email } =…

Validasi input artinya memeriksa data kiriman client sebelum dipakai: field wajib ada, tipenya benar, nilainya masuk akal. Bayangin petugas loket yang memeriksa formulir. Kalau ada kolom yang kosong atau salah, formulirnya dikembalikan dengan catatan di sebelah tiap kolom yang salah. Dia gak menerimanya dulu lalu bikin arsip berantakan. Di API, "catatan per kolom" itu adalah response error dengan pesan per field.

🧪 Tebakan cepat: validasi stok ditulis if (!req.body.stock) { errors.stock = ... }. Admin mengirim Bumi Manusia dengan "stock": 0, karena bukunya memang lagi habis. Lolos atau ditolak? Tebak dulu, jawabannya ada di bagian "Jangan andalkan truthy".

Kenapa server tetap harus memeriksa

Form di website Toko Buku mungkin sudah menolak harga negatif. Tapi form itu cuma satu jalan masuk. Siapa pun bisa mengirim request langsung ke API pakai curl atau Postman, melewati form-nya. Jadi validasi di frontend bisa di-bypass: dia ada untuk kenyamanan pengguna, sedangkan validasi di server adalah pertahanan terakhir. Ingat POST /greet yang menjawab "Halo, undefined!"? Itu endpoint tanpa validasi.

Aturan untuk buku baru

Field Aturan
title wajib, teks, minimal 2 karakter
price wajib, bilangan bulat, minimal 0
stock wajib, bilangan bulat, minimal 0
app.post("/books", (req, res) => {
  const { title } = req.body;
  const errors = {};

  if (title === undefined || title === "") {
    errors.title = ["The title field is required."];
  } else if (typeof title !== "string") {
    errors.title = ["The title field must be a string."];
  } else if (title.trim().length < 2) {
    errors.title = ["The title field must be at least 2 characters."];
  }

  for (const field of ["price", "stock"]) {
    const value = req.body[field];
    if (value === undefined) {
      errors[field] = [`The ${field} field is required.`];
    } else if (!Number.isInteger(value)) {
      errors[field] = [`The ${field} field must be an integer.`];
    } else if (value < 0) {
      errors[field] = [`The ${field} field must be at least 0.`];
    }
  }

  if (Object.keys(errors).length > 0) {
    return res.status(422).json({ message: "The given data was invalid.", errors });
  }

  // Lolos semua aturan: baru disimpan, seperti di pelajaran CRUD.
  const book = { ...req.body, id: nextId++ };
  books.push(book);
  res.status(201).json(book);
});

Jangan andalkan truthy

0, "", false, null, dan undefined semuanya falsy: dianggap false oleh if. Jadi if (!req.body.stock) menolak "stock": 0, padahal 0 itu stok yang sah. Bumi Manusia ditolak. Itu jawaban tebakannya.

Cek yang tepat menyebut maksudnya: "gak dikirim" ditulis value === undefined, "bukan bilangan bulat" ditulis !Number.isInteger(value), "negatif" ditulis value < 0. Dengan cek seperti itu, 0 lolos dan -5000 ditolak.

422 atau 400?

Dua-duanya artinya "salahnya di pengirim", tapi beda jenis:

Banyak API, termasuk Laravel, menjawab validasi gagal dengan 422. Sebagian API lain memakai 400 untuk dua-duanya, dan latihan di pelajaran ini juga memakai 400. Yang penting: kirim kode 4xx, jangan 200 (client mengira berhasil) dan jangan 500 (itu artinya server yang rusak).

Bentuk error per field

Untuk {"title": "A", "price": -5000, "stock": 0}, server di atas menjawab:

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json; charset=utf-8

Body-nya, dirapikan supaya gampang dibaca:

{
  "message": "The given data was invalid.",
  "errors": {
    "title": ["The title field must be at least 2 characters."],
    "price": ["The price field must be at least 0."]
  }
}

Di Laravel

$validated = $request->validate([
    'title' => 'required|string|min:2',
    'price' => 'required|integer|min:0',
    'stock' => 'required|integer|min:0',
]);

Satu baris itu melakukan semua cek di atas. Kalau ada yang gagal, Laravel otomatis menjawab 422 dengan bentuk yang sama, plus message yang diambil dari error pertama: "The title field must be at least 2 characters. (and 1 more error)". Request POST /api/siswa tanpa field nama yang required juga dijawab 422.

⚠️ Jebakan yang sering ketemu

TL;DR: Validasi di server wajib, karena frontend bisa di-bypass dengan curl atau Postman. Periksa tiap field (wajib ada, tipe, panjang atau nilai), kumpulkan semua kesalahan di object errors yang dikunci per nama field, lalu jawab sekali, misalnya {"message": "...", "errors": {"title": ["..."]}}. Body rusak yang gak bisa dibaca: 400 Bad Request. Body terbaca tapi melanggar aturan: 422 Unprocessable Content (Laravel memakai ini; latihan ini memakai 400). Jangan pakai cek truthy untuk angka: 0 itu falsy, jadi tulis === undefined dan Number.isInteger. Di Laravel, $request->validate([...]) menjawab 422 sendiri, asal client mengirim Accept: application/json.

Yang akan kamu pelajari