Laravel Validation dan Form Request

validasi input itu wajib di setiap aplikasi web. tanpa validasi, user bisa kirim data kosong, karakter berbahaya, atau nilai yang tidak masuk akal ke database. laravel menyediakan beberapa cara untuk handle validasi, mulai dari yang simpel di controller sampai Form Request terpisah yang reusable.
setelah pakai laravel beberapa tahun, pola yang saya pakai: validasi simpel di controller dengan $request->validate(), tapi untuk logika yang kompleks atau dipakai di banyak tempat, saya pisah ke Form Request. bedanya bukan cuma gaya penulisan, tapi juga struktur kode dan kemudahan testing.
1. Validasi Sederhana di Controller
cara paling cepat: panggil validate() di method controller. kalau gagal, laravel otomatis redirect kembali dengan pesan error.
<?php
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|max:255',
'body' => 'required',
'email' => 'required|email',
]);
Post::create($validated);
return redirect('/posts')->with('success', 'Post berhasil dibuat');
}
penjelasan per baris:
$request->validate([...]): jalankan validasi dengan aturan yang diberikan. kalau gagal, lempar ValidationException.'title' => 'required|max:255': fieldtitlewajib diisi dan maksimal 255 karakter. bisa digabung dengan tanda|.'email' => 'required|email': wajib diisi dan harus format email valid. laravel cek format dengan RFC standard.$validated: hanya field yang lolos validasi. aman dipakai untuk mass assignment, tidak ada field tambahan yang ikut.Post::create($validated): simpan post. karena$validatedsudah difilter, tidak ada field liar (mass assignment vulnerability).
Aturan Validasi yang Paling Sering Dipakai
| Aturan | Fungsi |
|---|---|
required | Wajib diisi |
string | Harus string |
integer | Harus bilangan bulat |
numeric | Harus angka (integer atau float) |
email | Format email valid |
min:n | Minimal n karakter/nilai |
max:n | Maksimal n karakter/nilai |
between:n,m | Antara n dan m |
in:a,b,c | Harus salah satu dari a, b, c |
unique:table,column | Belum ada di tabel database |
exists:table,column | Harus ada di tabel database |
regex:pattern | Cocok dengan regex |
confirmed | Harus ada field _confirmation yang cocok |
nullable | Boleh kosong, tapi kalau diisi divalidasi |
sometimes | Hanya validasi kalau field ada di request |
2. Menampilkan Error di Blade
di blade, pakai directive @error untuk cek apakah ada error di field tertentu:
<form method="POST" action="/posts">
@csrf
<div>
<label>Judul</label>
<input type="text" name="title" value="{{ old('title') }}">
@error('title')
<span class="text-red-500">{{ $message }}</span>
@enderror
</div>
<div>
<label>Email</label>
<input type="email" name="email" value="{{ old('email') }}">
@error('email')
<span class="text-red-500">{{ $message }}</span>
@enderror
</div>
<button type="submit">Simpan</button>
</form>
penjelasan per baris:
@csrf: token csrf untuk keamanan. wajib di setiap form POST. tanpa ini, laravel return 419.value="{{ old('title') }}": tampilkan nilai yang user ketik sebelumnya, supaya tidak perlu ngetik ulang kalau validasi gagal.@error('title')…@enderror: blok yang dirender hanya kalau fieldtitlegagal validasi.{{ $message }}: pesan error default dalam bahasa inggris.
3. Form Request
kalau validasi mulai kompleks atau dipakai di beberapa method, pisahkan ke class Form Request. lebih bersih, reusable, dan otomatis handle authorization.
php artisan make:request StorePostRequest
ini bikin satu file app/Http/Requests/StorePostRequest.php dengan method authorize() dan rules():
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return true; // izinkan semua user
}
public function rules(): array
{
return [
'title' => 'required|max:255',
'body' => 'required',
'email' => 'required|email',
];
}
}
lalu pakai di controller dengan type-hint:
<?php
use App\Http\Requests\StorePostRequest;
public function store(StorePostRequest $request)
{
// $request sudah tervalidasi otomatis
Post::create($request->validated());
return redirect('/posts');
}
penjelasan:
StorePostRequest $request: type-hint ke Form Request, bukanRequestbiasa. laravel otomatis jalankan validasi sebelum method controller dipanggil.$request->validated(): sama seperti$request->validate()tapi return array, dan method ini sudah di-handle oleh Form Request.- kalau validasi gagal, otomatis redirect (web) atau kirim 422 (api).
Method Penting di Form Request
| Method | Fungsi |
|---|---|
authorize() | Cek apakah user boleh akses. return true untuk selalu izinkan, atau logika custom |
rules() | Return array aturan validasi |
messages() | Override pesan error default |
attributes() | Override nama field untuk pesan error |
prepareForValidation() | Modifikasi data sebelum validasi |
passedValidation() | Modifikasi data setelah validasi |
4. Pesan Error Custom
default laravel pakai bahasa inggris. untuk bahasa indonesia, pakai salah satu dari dua cara:
Cara cepat per Form Request: override di class:
<?php
public function messages(): array
{
return [
'title.required' => 'Judul wajib diisi.',
'title.max' => 'Judul maksimal 255 karakter.',
'body.required' => 'Konten wajib diisi.',
'email.required' => 'Email wajib diisi.',
'email.email' => 'Format email tidak valid.',
];
}
public function attributes(): array
{
return [
'title' => 'judul',
'body' => 'konten',
];
}
penjelasan per method:
messages(): override pesan per aturan. key-nya formatfield.aturan. kalau tidak ada, laravel pakai pesan default.attributes(): ganti nama field di pesan error. tanpa ini, pesan “title” akan tampil apa adanya. dengan ini, jadi “judul”.
Cara global: publish lang dulu karena skeleton Laravel 11/12 tidak bawa folder lang secara default:
php artisan lang:publish
# akan buat folder lang/en/ dengan validation.php
lalu copy lang/en/validation.php jadi lang/id/validation.php dan terjemahkan, set di .env:
APP_LOCALE=id
APP_FALLBACK_LOCALE=en
atau di config/app.php 'locale' => env('APP_LOCALE', 'en'). setelah itu semua pesan error otomatis bahasa indonesia. cek lang/en/validation.php bagian attributes untuk ganti nama field global.
5. Custom Rule
kalau aturan bawaan tidak cukup, bikin sendiri. ada dua cara: pakai Rule::extend() untuk rule global, atau Closure untuk rule sekali pakai.
Custom Rule dengan Closure
<?php
public function rules(): array
{
return [
'username' => [
'required',
function ($attribute, $value, $fail) {
if (str_contains($value, 'admin')) {
$fail("Field {$attribute} tidak boleh mengandung kata 'admin'.");
}
},
],
];
}
penjelasan:
function ($attribute, $value, $fail): closure untuk validasi. parameter: nama field, nilai, callback untuk gagal.$fail("..."): panggil untuk tandai validasi gagal. pesan yang dikirim akan jadi pesan error.
Custom Rule dengan Class
untuk rule yang dipakai di banyak tempat, pisah ke class:
php artisan make:rule NoAdminWord
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class NoAdminWord implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (str_contains($value, 'admin')) {
$fail("Field {$attribute} tidak boleh mengandung kata 'admin'.");
}
}
}
lalu pakai di Form Request:
use App\Rules\NoAdminWord;
public function rules(): array
{
return [
'username' => ['required', new NoAdminWord],
];
}
6. Validasi Bersyarat (Conditional)
sering ada kebutuhan: field B wajib hanya kalau field A bernilai tertentu. pakai Rule::when() atau method sometimes().
<?php
use Illuminate\Validation\Rule;
public function rules(): array
{
return [
'jenis' => 'required|in:pribadi,perusahaan',
'nik' => 'required_if:jenis,pribadi',
'npwp' => 'required_if:jenis,perusahaan',
'alamat' => 'required_if:jenis,perusahaan|string',
];
}
penjelasan:
required_if:jenis,pribadi: fieldnikwajib hanya kalau fieldjenisbernilaipribadi. kalaujenis=perusahaan,nikboleh kosong.- ini aturan declarative, langsung ditulis di rules. tidak perlu method terpisah.
untuk logika lebih kompleks, pakai Rule::when():
use Illuminate\Validation\Rule;
public function rules(): array
{
return [
'payment_method' => 'required|in:transfer,qris,cod',
'bank_name' => [
Rule::when($this->input('payment_method') === 'transfer', 'required|string'),
],
];
}
Rule::when() cocok untuk kondisi yang lebih dinamis, misal ambil dari database.
7. Validasi Input Array
untuk input yang dikirim sebagai array (misal multiple checkbox atau repeater), pakai notasi dot.
public function rules(): array
{
return [
'items' => 'required|array|min:1',
'items.*.name' => 'required|string|max:100',
'items.*.price' => 'required|numeric|min:0',
'items.*.qty' => 'required|integer|min:1',
];
}
penjelasan:
'items' => 'required|array|min:1':itemswajib ada, harus array, minimal 1 elemen.'items.*.name': wildcard*cocokkan semua elemen array. validasinamedi setiap item.- cara ini juga bisa untuk nested array:
'items.*.options.*.value'.
8. Validasi Manual dengan Validator
kadang validasi perlu dijalankan di tempat yang tidak cocok dengan Form Request (misal di console command atau middleware custom). pakai Validator::make():
<?php
use Illuminate\Support\Facades\Validator;
$data = [
'email' => $request->input('email'),
'name' => $request->input('name'),
];
$validator = Validator::make($data, [
'email' => 'required|email',
'name' => 'required|string',
]);
if ($validator->fails()) {
return back()->withErrors($validator)->withInput();
}
$data = $validator->validated();
penjelasan:
Validator::make($data, $rules): bikin validator manual. parameter: data yang akan divalidasi, aturan.$validator->fails(): return true kalau ada error.$validator->validated(): return array hanya field yang lolos.
9. Validasi di API
untuk endpoint API, validasi gagal otomatis kirim response JSON 422, bukan redirect:
{
"message": "The title field is required. (and 1 more error)",
"errors": {
"title": ["The title field is required."],
"email": ["The email field must be a valid email address."]
}
}
cara handle di frontend:
const response = await fetch('/api/posts', {
method: 'POST',
body: formData,
});
if (response.status === 422) {
const data = await response.json();
// tampilkan error per field
for (const [field, messages] of Object.entries(data.errors)) {
console.log(`${field}: ${messages.join(', ')}`);
}
}
response 422 (Unprocessable Entity) adalah standar http untuk menandakan validasi gagal. frontend bisa parse data.errors untuk dapatkan pesan per field.
10. Modify Data Sebelum Validasi
kadang perlu bersih-bersih data dulu, misal trim spasi atau ubah ke lowercase. override prepareForValidation() di Form Request:
<?php
protected function prepareForValidation(): void
{
$this->merge([
'email' => strtolower(trim($this->email)),
'phone' => preg_replace('/[^0-9]/', '', $this->phone),
]);
}
penjelasan:
$this->merge([...]): tambahkan atau timpa field sebelum validasi.strtolower(trim($this->email)): trim spasi lalu lowercase, jadi “[email protected]” jadi “[email protected]”.preg_replace('/[^0-9]/', '', $this->phone): hapus semua karakter non-angka dari phone, jadi “+62 812-1234-5678” jadi “6281212345678”.
method ini jalan sebelum rules() dipanggil, jadi data yang divalidasi sudah bersih.
Kesimpulan
$request->validate([...])untuk validasi simpel di controller- Form Request untuk pisah logika validasi ke class terpisah, lebih reusable
messages()danattributes()untuk customisasi pesan errorRule::when()untuk validasi kondisional berdasarkan field lainrequired_ifuntuk validasi bersyarat sederhanaitems.*.namenotasi untuk validasi input array@errordirective di blade untuk tampilkan error per fieldlang:publish+config/app.phpuntuk globalisasi bahasa indonesia- custom rule dengan class
implements ValidationRuleuntuk logika reusable prepareForValidation()untuk modifikasi data sebelum validasi
Baca Juga Mengenai :
Pertanyaan yang Sering Diajukan
Apa beda validasi pakai validate() dan Form Request?
validate() cocok untuk validasi simpel yang langsung ditulis di controller. Form Request memisahkan logika validasi ke class terpisah, lebih bersih, reusable, dan otomatis handle authorization.
Bagaimana cara bikin pesan error bahasa Indonesia?
jalankan php artisan lang:publish untuk buat folder lang, lalu copy lang/en/validation.php ke lang/id/validation.php dan terjemahkan. set locale di config/app.php lewat APP_LOCALE di .env. skeleton Laravel 11/12 memang tidak bawa folder lang secara default.
Apa itu Form Request di Laravel?
Form Request adalah class turunan FormRequest yang dipakai untuk handle validasi dan authorization. dibuat pakai php artisan make:request StorePostRequest. setelah dibuat, tinggal type-hint di parameter controller.
Bagaimana cara bikin aturan validasi custom?
bikin class implement ValidationRule dengan method validate($attribute, $value, Closure $fail), atau pakai Closure langsung di rules. di Laravel 11/12 cara lama Rule dengan passes()/message() sudah diganti ValidationRule.
Bagaimana cara validasi input array atau nested?
pakai notasi dot: items.*.name, items.0.price. atau pakai array: items => required|array, items.name => required. Laravel otomatis handle keduanya.
Apa yang terjadi kalau validasi gagal?
Laravel otomatis lempar ValidationException, redirect kembali ke halaman form (untuk web) atau kirim response JSON 422 (untuk API). di blade, @error directive menampilkan pesan error per field.
Bagaimana cara validasi kondisional berdasarkan input lain?
pakai Rule::when() dan sometimes() di Form Request, atau if conditional di rules() method. misal field B wajib hanya kalau field A bernilai tertentu.
Bagaimana cara skip validasi untuk field tertentu?
pakai sometimes di rules, atau hapus field dengan $request->except() sebelum validasi. untuk Form Request, override prepareForValidation() untuk modifikasi data sebelum divalidasi.

