Validasi Input — Node.js

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

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 /catatan dari 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);
});
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);
});

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:

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

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.

Yang akan kamu pelajari