Laravel Validation dan Form Request

| | Laravel
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' : field title wajib 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 $validated sudah difilter, tidak ada field liar (mass assignment vulnerability).

Aturan Validasi yang Paling Sering Dipakai

AturanFungsi
requiredWajib diisi
stringHarus string
integerHarus bilangan bulat
numericHarus angka (integer atau float)
emailFormat email valid
min:nMinimal n karakter/nilai
max:nMaksimal n karakter/nilai
between:n,mAntara n dan m
in:a,b,cHarus salah satu dari a, b, c
unique:table,columnBelum ada di tabel database
exists:table,columnHarus ada di tabel database
regex:patternCocok dengan regex
confirmedHarus ada field _confirmation yang cocok
nullableBoleh kosong, tapi kalau diisi divalidasi
sometimesHanya 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 field title gagal 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, bukan Request biasa. 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

MethodFungsi
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 format field.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 : field nik wajib hanya kalau field jenis bernilai pribadi. kalau jenis=perusahaan, nik boleh 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' : items wajib ada, harus array, minimal 1 elemen.
  • 'items.*.name' : wildcard * cocokkan semua elemen array. validasi name di 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() dan attributes() untuk customisasi pesan error
  • Rule::when() untuk validasi kondisional berdasarkan field lain
  • required_if untuk validasi bersyarat sederhana
  • items.*.name notasi untuk validasi input array
  • @error directive di blade untuk tampilkan error per field
  • lang:publish + config/app.php untuk globalisasi bahasa indonesia
  • custom rule dengan class implements ValidationRule untuk 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.

Sigit Nurhanafi avatar
Full-stack developer & technical writer. Berpengalaman di PHP, Laravel, NodeJS, MySQL, dan Python. Aktif menulis tutorial pemrograman dan maintaining open-source projects di PemburuKode sejak 2021.