Validasi adalah memeriksa data kiriman client sebelum dipakai atau disimpan. Bayangin petugas loket yang menerima formulir. Kolom wajib kosong, tanggal lahir diisi huruf: formulirnya dikembalikan dengan catatan apa yang salah, dan belum diproses. Server juga begitu. Data dari luar bisa datang dari form yang rapi, dari curl, atau dari orang iseng, jadi server gak boleh percaya begitu saja.
🧪 Tebakan cepat: route
POST /catatandari pelajaran CRUD menerima body{"judul":"","isi":"lari 5 km"}. Apa jawabannya? Tebak dulu, jawabannya ada di bagian "Tanpa validasi, semua masuk".
Tanpa validasi, semua masuk
Route POST kita mengambil judul dan isi lalu langsung menyimpan:
| Body yang dikirim | Jawaban (tanpa validasi) |
|---|---|
{"judul":"","isi":"lari 5 km"} |
201 {"id":3,"judul":"","isi":"lari 5 km"} |
{"judul":123,"isi":"lari 5 km"} |
201 {"id":4,"judul":123,"isi":"lari 5 km"} |
{} |
201 {"id":5} |
Semuanya 201 Created. Catatan tanpa judul ikut tersimpan. Itu jawaban tebakannya. Data rusak seperti ini baru ketahuan nanti, waktu frontend mencoba menampilkan judul yang kosong atau angka.
Validasi manual: kumpulkan error, lalu 400
function validasiCatatan(req, res, next) {
const { judul, isi } = req.body;
const errors = [];
if (typeof judul !== "string" || judul.trim() === "") {
errors.push("judul wajib diisi");
} else if (judul.length > 50) {
errors.push("judul maksimal 50 karakter");
}
if (typeof isi !== "string") {
errors.push("isi harus teks");
}
if (errors.length > 0) {
return res.status(400).json({ errors });
}
next();
}
app.post("/catatan", validasiCatatan, (req, res) => {
const { judul, isi } = req.body;
const baru = { id: nextId++, judul, isi };
catatan.push(baru);
res.status(201).json(baru);
});
- Cek paling sederhana adalah
if (!req.body.judul): benar kalau field-nya gak dikirim atau kosong. Versi di atas lebih ketat. - Tiap aturan yang dilanggar menambah satu pesan ke array
errors, jadi client tahu semua yang salah sekaligus. typeof judul !== "string"menolak angka,null, dan field yang gak dikirim (undefined).judul.trim() === ""menolak judul yang cuma spasi.errors.length > 0artinya ada masalah: jawab400 Bad Requestdengan daftarnya, danreturnsupayanext()gak dipanggil.- Validasinya middleware, jadi bisa dipasang juga di
app.put("/catatan/:id", validasiCatatan, ...).
| Body | Jawaban |
|---|---|
{"judul":"","isi":"lari 5 km"} |
400 {"errors":["judul wajib diisi"]} |
{"judul":"Olahraga"} |
400 {"errors":["isi harus teks"]} |
{} |
400 {"errors":["judul wajib diisi","isi harus teks"]} |
{"judul":"Olahraga","isi":"lari 5 km"} |
201 {"id":3,"judul":"Olahraga","isi":"lari 5 km"} |
Zod: aturannya ditulis sebagai schema
Aturan yang sama bisa ditulis lebih pendek dengan Zod (npm install zod). Schema adalah deskripsi bentuk data yang diterima:
const { z } = require("zod");
const CatatanSchema = z.object({
judul: z.string().trim().min(1).max(50),
isi: z.string(),
});
app.post("/catatan", (req, res) => {
const hasil = CatatanSchema.safeParse(req.body);
if (!hasil.success) {
return res.status(400).json({ errors: z.flattenError(hasil.error).fieldErrors });
}
const baru = { id: nextId++, ...hasil.data };
catatan.push(baru);
res.status(201).json(baru);
});
safeParsegak melempar error. Dia mengembalikan{ success: true, data }atau{ success: false, error }.z.flattenError(hasil.error).fieldErrorsmengelompokkan pesan per field.hasil.dataadalah data yang sudah bersih:.trim()membuang spasi di pinggir, dan field yang gak ada di schema dibuang. Simpanhasil.data, bukanreq.body.
Pesan bawaan Zod 4, persis seperti yang dikirim:
| Body | Jawaban |
|---|---|
{"judul":"","isi":"lari 5 km"} |
400 {"errors":{"judul":["Too small: expected string to have >=1 characters"]}} |
{"judul":123,"isi":"lari 5 km"} |
400 {"errors":{"judul":["Invalid input: expected string, received number"]}} |
{"judul":"Olahraga"} |
400 {"errors":{"isi":["Invalid input: expected string, received undefined"]}} |
{"judul":" Olahraga ","isi":"lari 5 km"} |
201 {"id":3,"judul":"Olahraga","isi":"lari 5 km"} |
Library populer lain untuk validasi di Express: express-validator (aturan dipasang sebagai middleware di route) dan Joi (schema seperti Zod).
📌 400 atau 422?
Kebanyakan API Express menjawab data yang gak lolos validasi dengan 400 Bad Request. Sebagian API, misalnya Laravel, memakai 422 Unprocessable Content: request-nya terbaca, tapi isinya gak bisa diproses. Dua-duanya benar, yang penting konsisten di satu API.
Di latihan: validateUser tanpa server
Fungsi validateUser(data) di latihan bekerja seperti validasiCatatan, tapi mengembalikan { valid, errors } dan gak menjawab request. Aturan dan pesannya harus persis:
namewajib ada dan string, kalau gak:name wajib diisi.emailwajib ada dan mengandung@(cek dengandata.email.includes("@")), kalau gak:email harus valid.ageboleh gak ada, tapi kalau ada harus number dan lebih dari 0, kalau gak:age harus angka positif. Cek dengandata.age !== undefined && (typeof data.age !== "number" || data.age <= 0).
valid adalah errors.length === 0. Kode awal mencetak tiga tes:
Test 1 (valid): {"valid":true,"errors":[]}
Test 2 (invalid): {"valid":false,"errors":["name wajib diisi","email harus valid"]}
Test 3 (age negatif): {"valid":false,"errors":["age harus angka positif"]}
⚠️ Jebakan yang sering ketemu
- Validasi cuma di browser. Form dengan
requiredgak menghalangicurl. Validasi di server wajib, yang di browser cuma untuk kenyamanan. if (!data.age)untuk field opsional.0itu falsy, jadi umur 0 lolos tanpa diperiksa. Bandingkan denganundefined, seperti di atas.- Menyimpan
req.bodysetelah Zod.hasil.datayang sudah di-trim dan dibersihkan,req.bodymasih mentah. - Pesan beda satu huruf. Client dan tes membaca pesannya persis.
email harus validbeda denganEmail harus valid.
TL;DR: Jangan percaya data dari client: periksa sebelum disimpan. Tanpa validasi, {"judul":""} tetap dijawab 201 dan tersimpan. Validasi manual mengumpulkan pesan di array errors, lalu if (errors.length > 0) return res.status(400).json({ errors }), dan lebih rapi sebagai middleware. Zod menulis aturannya sebagai schema (z.string().trim().min(1).max(50)), safeParse mengembalikan success dan data atau error, dan pesannya seperti Too small: expected string to have >=1 characters. Simpan hasil.data. Library lain: express-validator dan Joi. Status untuk data yang salah: 400 Bad Request.