Validasi input berarti mengecek setiap data yang datang dari luar terhadap aturan yang jelas, sebelum data itu dipakai. Bayangin teller bank yang menerima formulir setoran: nama harus terisi, nomor rekening harus 10 digit, jumlahnya harus angka positif. Formulir yang salah dikembalikan dengan catatan di kolom yang salah, gak diperbaiki diam-diam. Dan walaupun aplikasi bank di HP-mu sudah mengecek formulir itu, teller tetap mengecek ulang, karena orang bisa datang membawa formulir buatan sendiri.
🧪 Tebakan cepat: form daftarmu menolak umur di bawah 13 lewat validasi di browser. Seseorang mengirim
{"age": 10}langsung ke API pakaicurl, melewati form. Server yang gak memvalidasi menerima data itu atau gak? Tebak dulu, jawabannya ada di bagian "Client untuk UX, server untuk keamanan".
Aturan untuk tiap field
Di Lab dan di latihan, form daftar akun punya tiga field. Ini aturannya:
| Field | Aturan |
|---|---|
name |
wajib ada, berupa teks, 2 sampai 50 karakter |
email |
wajib ada, berupa teks, mengandung @ dan . |
age |
wajib ada, berupa angka bulat, 13 sampai 120 |
Tiga prinsip di baliknya:
- Allowlist, bukan blocklist: tulis apa yang boleh (angka 13 sampai 120), bukan menebak semua yang gak boleh. Input aneh yang gak kepikiran otomatis tertolak.
- Tolak, jangan perbaiki: kalau umurnya
"dua puluh", kembalikan error. Jangan menebak maksudnya. - Validasi seawal mungkin: cek di pintu masuk, sebelum data menyentuh database, file, atau query.
Validasi manual dengan JavaScript
function validateInput(data) {
const errors = []
if (typeof data.name !== 'string' || data.name.length < 2 || data.name.length > 50) {
errors.push('nama harus 2 sampai 50 karakter')
}
if (typeof data.email !== 'string' || !data.email.includes('@') || !data.email.includes('.')) {
errors.push('email harus mengandung @ dan .')
}
if (!Number.isInteger(data.age) || data.age < 13 || data.age > 120) {
errors.push('umur harus angka bulat 13 sampai 120')
}
return { valid: errors.length === 0, errors }
}
typeof data.name !== 'string'menangkap field yang gak ada (undefined) atau tipenya salah. Baru setelah itu.lengthaman dibaca.data.name.length > 50adalah batas atas. Batas bawah saja gak cukup: nama 56 karakter juga harus ditolak.Number.isInteger(data.age)bernilaifalseuntukundefined, teks"25",NaN, dan25.5. Jadi umur yang gak dikirim sama sekali ikut tertolak.{ valid: errors.length === 0, errors }: kalau gak ada error, hasilnya persis{ valid: true, errors: [] }.
Ini hasilnya untuk data yang sama dengan di Lab:
| Input | Hasil |
|---|---|
{ name: 'Budi', email: '[email protected]', age: 25 } |
{ valid: true, errors: [] } |
{ name: '', email: 'invalid', age: 10 } |
valid: false, 3 error: nama, email, umur |
{ name: 'A', email: '[email protected]', age: 25 } |
valid: false, nama terlalu pendek |
{ name: 'Budi', email: '[email protected]' } |
valid: false, umur gak ada |
Schema dengan Zod
Menulis if untuk tiap aturan cepat jadi panjang. Schema adalah cetakan aturan yang ditulis sekali, lalu dipakai untuk mengecek data. Zod adalah library schema yang populer di JavaScript dan TypeScript:
import { z } from 'zod'
const UserSchema = z.object({
name: z.string().min(2).max(50),
email: z.email(),
age: z.number().int().min(13).max(120),
})
app.post('/api/users', (req, res) => {
const result = UserSchema.safeParse(req.body)
if (!result.success) {
return res.status(422).json({ errors: z.flattenError(result.error).fieldErrors })
}
db.users.insert(result.data)
res.status(201).json(result.data)
})
z.number().int().min(13).max(120)dibaca dari kiri: angka, bulat, minimal 13, maksimal 120. Kalau aturannya cuma "gak boleh negatif", tulisz.number().min(0).max(120)..nonnegative()artinya sama dengan.min(0), sedangkan.positive()berarti lebih dari 0.safeParsegak melempar error. Dia mengembalikan{ success: true, data }atau{ success: false, error }.- 422 Unprocessable Content adalah status untuk "format request-nya benar, tapi isinya gak lolos aturan".
Untuk { name: '', email: 'invalid', age: 10 }, Zod 4 menjawab dengan pesan ini, persis seperti yang tampil di Lab:
{
"errors": {
"name": ["Too small: expected string to have >=2 characters"],
"email": ["Invalid email address"],
"age": ["Too small: expected number to be >=13"]
}
}
Di Laravel
class StoreUserRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => ['required', 'string', 'min:2', 'max:50'],
'email' => 'required|email|max:255',
'age' => ['required', 'integer', 'min:13', 'max:120'],
];
}
}
- Form Request adalah class Laravel yang memegang aturan validasi. Controller yang memakainya baru jalan kalau semua aturan lolos. Kalau gagal, Laravel otomatis menjawab
422dengan pesan error per field. - Rule
emailmengecek format email.email:rfc,dnslebih ketat: ikut mengecek domain email-nya punya record MX.
Client untuk UX, server untuk keamanan
Validasi di browser bisa dilewati: lewat DevTools, curl, Postman, atau proxy seperti Burp Suite. Jadi server yang gak memvalidasi menerima {"age": 10}, dan data salah itu masuk database. Itu jawaban tebakannya.
- Validasi client memberi umpan balik cepat sebelum request dikirim. Itu soal UX.
- Validasi server adalah satu-satunya yang benar-benar menahan data. Itu soal keamanan, dan wajib.
- Jadi lakukan keduanya. Dengan Zod, schema yang sama bisa dipakai di dua sisi.
Yang sering lupa divalidasi
- Ukuran body:
app.use(express.json({ limit: '1mb' }))menolak body raksasa sebelum dibaca. - Panjang teks dan jumlah isi array: misalnya
'items' => ['required', 'array', 'max:50']di Laravel, atauz.array(...).max(50). - Parameter URL: id, slug, dan nomor halaman juga input dari luar.
⚠️ Jebakan yang sering ketemu
- Cuma validasi di frontend.
curlmelewatinya dalam satu baris. Server harus selalu mengecek ulang. - Cuma batas bawah.
name.length < 2tanpa> 50meloloskan nama 56 karakter. - Menganggap field yang gak ada itu lolos.
if (data.age && data.age < 13)melewatkan umur yang gak dikirim. Cek tipenya dulu, misalnya denganNumber.isInteger. - Angka dari form masih berupa teks. Nilai
<input>selalu string, jadi"25"gak lolosz.number(). Ubah dulu denganNumber(...), atau pakaiz.coerce.number().
TL;DR: Validasi setiap input dari luar dengan aturan allowlist, tolak yang salah, dan lakukan seawal mungkin. Fungsi manual mengumpulkan error ke array dan mengembalikan { valid: errors.length === 0, errors }: nama 2 sampai 50 karakter, email mengandung @ dan ., umur angka bulat 13 sampai 120, dan field yang gak ada ikut ditolak. Schema seperti Zod menulis aturan sekali (z.number().int().min(13).max(120), atau .min(0) untuk "gak negatif") dan safeParse memberi error per field untuk dijawab dengan 422. Di Laravel, Form Request dengan rule seperti required|email|max:255. Validasi client untuk UX, validasi server untuk keamanan: lakukan keduanya, karena client bisa dilewati.