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);
});
errorsdimulai sebagai object kosong. Tiap field yang gagal menambah key dengan namanya sendiri, jadi semua kesalahan terkumpul sekaligus, bukan berhenti di yang pertama.typeof title !== "string"dicek sebelum.trim(), karena angka gak punya.trim().title.trim()membuang spasi di depan dan belakang, supaya" "gak dihitung 2 karakter.Number.isInteger(value)hanyatrueuntuk angka bulat:95000lolos,"95000"(teks) dan95000.5ditolak.Object.keys(errors).length > 0artinya ada minimal satu error.returnmenghentikan handler, jadi data yang salah gak pernah sampai kebooks.push.
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:
400 Bad Request: request-nya sendiri rusak, server gak bisa membacanya. Contoh: body{"title": "Bumi Manusia",yang kurung kurawalnya gak ditutup. Express menolaknya dengan 400 sebelum handler-mu jalan.422 Unprocessable Content: request-nya terbaca, JSON-nya rapi, tapi isinya melanggar aturan. Contoh:{"title": "A", "price": -5000, "stock": 0}. Nama lamanya Unprocessable Entity, dan Node masih menulis nama itu. Kodenya sama: 422.
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."]
}
}
messageadalah ringkasan satu kalimat.errorsdikunci per nama field, isinya daftar pesan. Frontend tinggal menaruh tiap pesan di bawah kolom yang namanya sama.stock: 0lolos, jadi gak muncul dierrors.
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
- Cek truthy untuk angka.
if (!stock)menolak 0. Tulisstock === undefinedatau!Number.isInteger(stock). - Berhenti di error pertama.
returndi tiapifbikin client baru tahu kesalahan berikutnya setelah memperbaiki yang pertama. Kumpulkan dulu dierrors, jawab sekali. - Validasi gagal dijawab 200 atau 500.
res.json({ error: "..." })berstatus 200, client mengira sukses. Pakaires.status(422)(atau400kalau API-nya memakai itu). - Laravel menjawab 302, bukan 422. Tanpa header
Accept: application/json, Laravel mengira request-nya dari form HTML dan me-redirect balik. Client API harus mengirim header itu.
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.