Validasi adalah garis pertahanan pertama aplikasi Anda: ia memastikan data yang masuk sesuai harapan sebelum menyentuh database. Laravel menyediakan sistem validasi yang sangat lengkap, tapi banyak developer menumpuk semuanya di controller sampai berantakan. Artikel ini menunjukkan cara memvalidasi form dengan rapi di Laravel 10/11 — dari yang paling sederhana hingga rule kustom.
1. Validasi Inline dengan validate()
Cara tercepat: panggil $request->validate() langsung di controller. Jika gagal, Laravel otomatis mengembalikan pengguna ke halaman sebelumnya beserta pesan error dan input lama.
public function store(Request $request)
{
$data = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users,email',
'age' => 'nullable|integer|min:17',
]);
User::create($data);
return back()->with('success', 'Data tersimpan.');
}
Nilai kembalian validate() hanya berisi field yang lolos aturan — aman untuk langsung dipakai create().
2. Aturan Validasi yang Sering Dipakai
required/nullable— wajib atau boleh kosong.email,numeric,integer,string,boolean— tipe data.min:3/max:255— panjang string atau nilai angka.unique:users,email/exists:categories,id— cek ke database.confirmed— cocok untuk password (butuh fieldpassword_confirmation).in:draft,published— nilai harus salah satu dari daftar.
3. Form Request untuk Controller yang Bersih
Saat aturan bertambah banyak, pindahkan ke kelas Form Request agar controller tetap ramping:
php artisan make:request StoreArticleRequest
Isi method authorize() dan rules():
public function authorize(): bool
{
return true; // ganti dengan logika izin bila perlu
}
public function rules(): array
{
return [
'title' => 'required|string|max:150',
'body' => 'required|string',
'status' => 'required|in:draft,published',
'tags' => 'array',
'tags.*' => 'string|max:30',
];
}
Lalu cukup type-hint di controller — validasi berjalan otomatis sebelum method dijalankan:
public function store(StoreArticleRequest $request)
{
Article::create($request->validated());
return redirect()->route('articles.index');
}
4. Pesan dan Nama Atribut Kustom
Pesan error bawaan berbahasa Inggris. Timpa dengan method messages() dan attributes() di Form Request:
public function messages(): array
{
return [
'title.required' => 'Judul wajib diisi.',
'title.max' => 'Judul maksimal 150 karakter.',
];
}
public function attributes(): array
{
return [
'title' => 'judul artikel',
];
}
Dengan attributes(), pesan generik seperti "The title field is required" berubah menjadi "The judul artikel field is required" — berguna untuk placeholder :attribute.
5. Aturan Validasi Kustom
Untuk logika unik, buat rule sendiri:
php artisan make:rule Uppercase
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (strtoupper($value) !== $value) {
$fail('The :attribute harus huruf kapital.');
}
}
Pakai di aturan seperti rule bawaan:
'code' => ['required', new Uppercase],
Untuk validasi sekali pakai, closure juga bisa langsung ditulis inline:
'slug' => [
'required',
function ($attribute, $value, $fail) {
if (str_contains($value, ' ')) {
$fail('Slug tidak boleh mengandung spasi.');
}
},
],
6. Menampilkan Error di Blade
Laravel menyimpan error di variabel $errors yang selalu tersedia di setiap view. Tampilkan per field dan pertahankan input lama dengan old():
<input type="text" name="title" value="{{ old('title') }}">
@error('title')
<span class="text-red-500">{{ $message }}</span>
@enderror
Untuk menampilkan semua error sekaligus di atas form:
@if ($errors->any())
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endif
7. Validasi Array dan File
Gunakan notasi titik * untuk memvalidasi tiap elemen array, dan aturan file untuk unggahan:
$request->validate([
'items' => 'required|array|min:1',
'items.*.name' => 'required|string',
'items.*.qty' => 'required|integer|min:1',
'avatar' => 'required|image|mimes:jpg,png|max:2048',
]);
Aturan max:2048 pada file dihitung dalam kilobyte, jadi contoh di atas membatasi ukuran gambar hingga 2 MB.
8. Form Request untuk Aturan yang Mulai Kompleks
Ketika aturan validasi keluar dari controller, memindahkannya ke Form Request adalah langkah berikutnya. Aturan, pesan, dan atribut terkumpul di satu kelas yang bisa dipakai ulang dan diuji terpisah:
php artisan make:request StoreProductRequest
class StoreProductRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()?->can('create', Product::class) ?? false;
}
public function rules(): array
{
return [
'name' => 'required|string|max:120',
'slug' => 'nullable|string|unique:products,slug',
'price' => 'required|numeric|min:0',
'category_id' => 'required|exists:categories,id',
'description' => 'nullable|string',
'is_active' => 'sometimes|boolean',
];
}
public function messages(): array
{
return [
'name.required' => 'Nama produk wajib diisi.',
'price.min' => 'Harga tidak boleh nol.',
'slug.unique' => 'Slug ini sudah dipakai produk lain.',
];
}
public function attributes(): array
{
return [
'category_id' => 'kategori',
'description' => 'deskripsi',
];
}
// Bersihkan input sebelum masuk database.
protected function prepareForValidation(): void
{
if ($this->filled('slug')) {
$this->merge(['slug' => Str::slug($this->input('slug'))]);
}
}
}
Controller jadi ramping karenavalidasi, otorisasi, dan pembersihan input terjadi sebelum method dipanggil:
public function store(StoreProductRequest $request)
{
$product = Product::create($request->validated());
return redirect()->route('products.show', $product)
->with('success', 'Produk disimpan.');
}
Method prepareForValidation() sering dilupakan. Ini titik paling murah untuk menormalkan input: memangkas spasi, mengubah slug, atau mengubah tanggal agar konsisten. Karena berjalan sebelum aturan dijalankan, data yang sampai ke validator sudah rapi.
Untuk form yang hanya dipakai sekali, Form Request terasa berlebihan. Patterns teknis ini, cara lengkapnya bisa dibaca di artikel CRUD Laravel 11 dari nol.
9. Validasi Bersyarat: Sometimes, Required If, Nullable
Banyak aturan hanya berlaku dalam kondisi tertentu. Salah memilih operator sering membuat validasi terlalu longgar atau terlalu ketat:
| Aturan | Arti | Kapan dipakai |
|---|---|---|
required | Wajib ada dan tidak boleh kosong | Nama, email, password |
nullable | Boleh kosong, tapi jika diisi harus sesuai aturan | Nomor telepon opsional |
sometimes | Hanya divalidasi bila field dikirim | Field yang hanya muncul di form edit |
required_if | Wajib bila field lain bernilai tertentu | Alasan penolakan wajib bila status = ditolak |
required_with | Wajib bila field lain ada | Detail wajib bila produk dipilih |
prohibited_if | Dilarang berisi nilai tertentu | Cegah pengguna mengubah peran sendiri |
Contoh aturan untuk register dan edit yang biasanya muncul bersamaan:
$request->validate([
'email' => 'required|email|unique:users,email,' . $user->id,
'password' => 'nullable|min:8|confirmed',
'current_password' => 'required_with:password|required_if:password,null|current_password',
'role' => 'sometimes|in:admin,editor,viewer',
'reason' => 'required_if:status,ditolak|nullable|string|max:255',
]);
Tiga pola di sini sering menjadi sumber bug. Pertama, unique saat edit harus mengecualikan record sendiri, kalau tidak user tidak bisa menyimpan tanpa mengubah slug. Kedua, password memakai nullable supaya form edit tidak memaksa password lama. Ketiga, required_if:status,ditolak membuat pesan error otomatis berbunyi "field reason wajib isi ketika status bernilai ditolak" — cukup jelas tanpa menulis pesan manual.
10. Menyesuaikan Pesan Error agar Ramah Pengguna
Pesan bawaan Laravel berbahasa Inggris dan menyebut nama field apa adanya. Dua cara memperbaikinya:
// 1. Pesan per aturan, dipisah dari controller
$request->validate($rules, $messages, $attributes);
// 2. atau publish semua pesan ke bahasa
php artisan lang:publish
// lalu edit resources/lang/id/validation.php
Menerjemahkan file validation.php memberi hasil paling konsisten karena berlaku di seluruh aplikasi sekaligus, termasuk validasi otomatis dari Form Request. Yang perlu diterjemahkan bukan hanya required, tetapi juga exists, unique, confirmed, accepted, dan password — aturan yang paling sering muncul dan paling membingungkan bagi pengguna awam.
Jangan pernah menampilkan pesan error mentah ke pengguna. Itu membocorkan nama kolom database dan membuat tampilan terlihat rusak. Tampilkan pesan yang sudah ditulis bersih, dan simpan detail teknisnya di log:
@if ($errors->has('email'))
<div role="alert" class="text-red-600 text-sm">{{ $errors->first('email') }}</div>
@endif
Tambahkan atribut aria-invalid dan hubungkan pesan dengan input memakai aria-describedby agar pembaca layar membacakan kesalahannya, bukan hanya melihat kotak merah.
11. Menguji Validasi agar Tidak Pecah Diam-diam
Validasi yang salah biasanya lolos ke produksi karena tidak ada tes. Dua kasus ini menutup hampir semua celah yang sering terlewat:
public function test_name_wajib_diisi(): void
{
$this->post('/products', ['price' => 10000])
->assertSessionHasErrors('name');
}
public function test_slug_unik_ignore_diri_sendiri(): void
{
$product = Product::factory()->create(['slug' => 'kopi']);
$this->actingAs(User::factory()->create())
->put("/products/{$product->id}", ['name' => 'Kopi', 'slug' => 'kopi'])
->assertSessionHasNoErrors();
}
Tes kedua adalah tes yang paling sering gagal. Setelah unique diperbaiki dengan mengecualikan $user->id, mudah lupa melakukan hal serupa pada unique milik produk — hasilnya pengguna tidak bisa pernah menyimpan perubahan. Menulis tes untuk setiap aturan unique jauh lebih murah daripada menerima laporan bug setelah launched.
Kesimpulan
Mulailah dengan validate() inline untuk form sederhana, lalu pindah ke Form Request begitu aturan bertambah. Ingat tiga hal ini: gunakan Form Request beserta prepareForValidation() untuk input yang perlu dinormalkan, pilih operator yang tepat (nullable, sometimes, required_if) supaya aturan tidak sekadar lebih ketat, dan teruskan pesan error dalam bahasa pengguna. Tambahkan tes untuk setiap aturan unik — di situlah bug validasi paling sering bersembunyi. Dengan pola ini validasi Anda tetap terpusat, mudah diuji, dan controller tetap bersih.