Masukan yang datang dari klien, baik body permintaan HTTP, parameter URL, header, maupun query string, tidak boleh dipercaya apa adanya sebelum diproses server. Topik ini membahas prinsip tidak memercayai klien, validasi berbasis schema dengan pustaka zod, penggunaan safeParse untuk mendapatkan laporan error yang terstruktur, serta penyatuan validasi ke dalam middleware Express. Setelah membaca topik ini, schema validasi dapat ditulis untuk objek masukan, laporan error dapat diproses menjadi respons JSON yang konsisten, dan kebiasaan menolak masukan yang tidak valid sebelum mencapai logika bisnis dapat diterapkan.
Prinsip tidak memercayai klien#
Setiap data dari klien dianggap tidak sah sampai dibuktikan sebaliknya. Asumsi ini berlaku untuk seluruh jalur masuk aplikasi: req.body, req.query, req.params, header permintaan, berkas yang diunggah, hingga nilai pada cookie. Klien dapat dipakai oleh siapa saja, termasuk pengguna yang mencoba mengirim data di luar form biasa lewat curl atau skrip otomatis. JavaScript pada sisi peramban, sekalipun ada, tidak menjadi pagar karena pengguna dapat mematikannya atau mengirim permintaan langsung ke endpoint.
Konsekuensi dari prinsip ini berlapis. Validasi sisi klien tetap berguna untuk pengalaman pengguna (umpan balik cepat), tetapi tidak boleh menjadi satu-satunya lapisan. Server wajib memvalidasi ulang setiap nilai sebelum nilai itu dipakai pada logika bisnis, kueri basis data, atau respons. Tipe data yang dikirim klien berbentuk string sampai server memutuskan sebaliknya, sehingga angka, tanggal, dan boolean perlu dikonversi secara eksplisit lewat schema, bukan lewat asumsi.
Lapisan validasi server paling efektif ditempatkan pada batas masuk, yaitu sebelum data mencapai handler. Pola ini memastikan seluruh handler menerima data yang sudah lolos schema sehingga kode bisnis tidak perlu menulis pemeriksaan berulang di setiap rute. Bila validasi hanya dilakukan di dalam handler, setiap rute baru membuka kemungkinan celah karena penulisnya lupa atau menulis pemeriksaan yang tidak konsisten.
Schema validasi dengan zod#
zod adalah pustaka validasi berbasis schema yang menyatakan bentuk data sebagai objek JavaScript. Schema didefinisikan sekali, lalu dipakai untuk memvalidasi masukan dan menghasilkan tipe TypeScript apabila dipakai bersama TypeScript. Pendekatan schema menggantikan pemeriksaan bertubi-tubi seperti if (typeof body.umur === "number" && body.umur > 0) dengan deklarasi z.number().int().positive().
Schema dapat menyatakan tipe data dasar, batasan, enum, objek bersarang, serta array. Setiap aturan membawa pesan error kustom yang dapat ditulis dalam bahasa Indonesia agar laporan yang dikembalikan kepada klien dapat dipahami pengguna akhir. Schema berikut memvalidasi objek pengguna dengan empat bidang.
const { z } = require("zod");
const SkemaPengguna = z.object({
nama: z.string().min(2, "Nama minimal 2 karakter"),
umur: z.number().int().positive("Umur harus bilangan bulat positif"),
email: z.string().email("Email tidak valid"),
peran: z.enum(["admin", "editor", "pembaca"]),
});
const masukanValid = {
nama: "Citra",
umur: 24,
email: "[email protected]",
peran: "editor",
};
const masukanInvalid = {
nama: "A",
umur: -5,
email: "bukan-email",
peran: "supervisor",
};
console.log("--- Masukan valid ---");
const hasilValid = SkemaPengguna.safeParse(masukanValid);
console.log("sukses:", hasilValid.success);
if (hasilValid.success) {
console.log("data:", JSON.stringify(hasilValid.data));
}
console.log("\n--- Masukan invalid ---");
const hasilInvalid = SkemaPengguna.safeParse(masukanInvalid);
console.log("sukses:", hasilInvalid.success);
if (!hasilInvalid.success) {
for (const isu of hasilInvalid.error.issues) {
console.log(`- ${isu.path.join(".")}: ${isu.message}`);
}
}--- Masukan valid ---
sukses: true
data: {"nama":"Citra","umur":24,"email":"[email protected]","peran":"editor"}
--- Masukan invalid ---
sukses: false
- nama: Nama minimal 2 karakter
- umur: Umur harus bilangan bulat positif
- email: Email tidak valid
- peran: Invalid enum value. Expected 'admin' | 'editor' | 'pembaca', received 'supervisor'Pada masukan valid, safeParse mengembalikan objek { success: true, data } dengan data berisi nilai yang sudah lolos schema. Pada masukan invalid, safeParse mengembalikan { success: false, error } dengan error.issues berisi daftar masalah, masing-masing memuat path (jalur bidang), message (pesan), dan code (jenis pelanggaran). Pemisahan ini membuat handler dapat memilih cabang tanpa perlu try/catch.
safeParse dan parse#
zod menyediakan dua metode utama untuk memvalidasi. safeParse tidak melempar error dan selalu mengembalikan objek hasil, sehingga cocok untuk middleware Express yang harus memberikan respons terstruktur. parse melempar ZodError bila masukan tidak valid, sehingga cocok untuk titik di mana error yang dilempar akan ditangkap oleh middleware penanganan error.
// tidak-dijalankan
// safeParse: cabang berdasarkan hasil
const hasil = SkemaPengguna.safeParse(body);
if (!hasil.success) {
return res.status(400).json({ error: hasil.error.issues });
}
// hasil.data siap dipakai di bawah
// parse: melempar ZodError bila invalid
const data = SkemaPengguna.parse(body); // lempar bila gagal
Pilihan antara safeParse dan parse bergantung pada struktur aplikasi. Middleware yang memvalidasi di batas rute biasanya memakai safeParse agar dapat mengembalikan respons 400 tanpa membungkus pemanggilan dengan try/catch. Handler yang memproses data internal, di mana masukan sudah pernah divalidasi atau berasal dari sumber yang dipercaya, dapat memakai parse sebagai pelindung tambahan yang akan memicu middleware penanganan error bila terjadi pelanggaran tak terduga.
Penyatuan ke middleware Express#
Schema validasi paling bermanfaat bila dipasang pada middleware yang berdiri di depan handler rute. Middleware membaca req.body, menjalankan safeParse, lalu menempatkan data hasil validasi ke req.body yang baru atau ke properti kustom seperti req.validated. Bila masukan tidak valid, middleware langsung mengembalikan respons 400 dengan daftar issues tanpa memanggil next().
// tidak-dijalankan
function validasiBody(schema) {
return (req, res, next) => {
const hasil = schema.safeParse(req.body);
if (!hasil.success) {
return res.status(400).json({
error: "MASUKAN_TIDAK_VALID",
detail: hasil.error.issues.map((i) => ({
bidang: i.path.join("."),
pesan: i.message,
})),
});
}
req.validated = hasil.data;
next();
};
}
// pemakaian pada rute
router.post("/pengguna", validasiBody(SkemaPengguna), handlerPengguna);Middleware ini memisahkan tanggung jawab: handler handlerPengguna hanya menerima req.validated yang sudah bersih, sehingga kode bisnisnya fokus pada logika simpan atau kueri. Daftar detail pada respons 400 dipetakan menjadi pasangan bidang dan pesan agar klien dapat menampilkan kesalahan per bidang pada form. Bentuk respons ini juga dapat dibuat konsisten lintas rute hanya dengan memakai validasiBody di rute lain.
Kesalahan umum#
Mengandalkan tipe data JSON sebagai jaminan. JSON hanya mengenal string, number, boolean, null, array, dan object, tetapi tidak mengenal konsep seperti “email valid”, “bilangan bulat positif”, atau “string tidak kosong”. Sebuah field yang seharusnya berisi umur dapat dikirim sebagai string "24" atau sebagai angka 24, dan klien yang beritikad buruk dapat mengirim "abc". Validasi dengan z.number().int().positive() menolak keduanya, sedangkan parseInt tanpa validasi schema menerima "24" dan "abc" menjadi NaN.
Menulis pemeriksaan bertubi-tubi di dalam handler alih-alih schema terpusat. Pola if (!body || !body.email || typeof body.email !== "string" || !body.email.includes("@")) berulang pada setiap rute dan mudah menyimpang antara satu rute dengan rute lain. Schema zod menggantikan seluruh pemeriksaan itu menjadi satu deklarasi yang dapat dipakai ulang, sehingga perubahan aturan (misalnya menambah panjang minimum) cukup dilakukan pada satu tempat.
Memvalidasi tipe data saja tetapi melupakan batasan nilai. z.number() menerima 0, -5, dan 3.14, padahal banyak bidang memerlukan batasan tambahan seperti int(), positive(), atau min(0). Demikian pula z.string() menerima string kosong. Tanpa min(1) atau nonempty(), bidang wajib dapat lolos sebagai string kosong yang kemudian menyebabkan error pada basis data atau logika bisnis.
Ringkasan#
- Masukan dari klien tidak boleh dipercaya apa adanya. Validasi sisi klien hanya untuk pengalaman pengguna, sedangkan keputusan akhir apakah masukan diterima diambil oleh server.
- zod menyatakan bentuk data sebagai schema yang dapat dipakai ulang, menggantikan pemeriksaan bertubi-tubi di setiap handler dengan deklarasi seperti
z.string().email()danz.number().int().positive(). safeParsemengembalikan objek hasil tanpa melempar error, sehingga cocok untuk middleware yang menyusun respons 400 darierror.issues.parsemelemparZodErrordan cocok untuk titik yang mengandalkan middleware penanganan error.- Middleware
validasiBody(schema)memusatkan validasi pada batas rute, menempatkan hasil bersih padareq.validated, dan menjaga handler tetap fokus pada logika bisnis.
Referensi#
- Zod documentation — zod
— Dokumentasi resmi zod mencakup pembuatan schema, metode
safeParsedanparse, serta pesan error kustom. - SafeParse — zod
— Referensi API
safeParseyang mengembalikan objek hasil alih-alih melempar error. - Writing Express middleware — Express.js
— Panduan resmi tentang penulisan middleware Express, termasuk pola
next()dan respons awal.