Validasi Form di Laravel dengan Rapi

Validasi adalah garis pertahanan pertama aplikasi Anda: ia memastikan data yang masuk sesuai harapan sebelum menyentuh database. Laravel menyediakan sistem validasi yang sangat len...

Validasi Form di Laravel dengan Rapi

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 field password_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:

AturanArtiKapan dipakai
requiredWajib ada dan tidak boleh kosongNama, email, password
nullableBoleh kosong, tapi jika diisi harus sesuai aturanNomor telepon opsional
sometimesHanya divalidasi bila field dikirimField yang hanya muncul di form edit
required_ifWajib bila field lain bernilai tertentuAlasan penolakan wajib bila status = ditolak
required_withWajib bila field lain adaDetail wajib bila produk dipilih
prohibited_ifDilarang berisi nilai tertentuCegah 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.

Ingin langsung mempraktikkannya?

Buat akun gratis untuk mengunduh contoh aplikasi Laravel, menyimpan tutorial favorit, dan mencoba kursus. Gratis, bisa daftar dengan akun Google.

Daftar Gratis Lihat katalog aplikasi
Yudhi
Ditulis oleh
Yudhi
Founder & Lead Developer, GudangCode

Yudhi adalah founder GudangCode dan developer Laravel dengan pengalaman membangun puluhan sistem informasi bisnis siap pakai — mulai dari POS, HRIS, hingga aplikasi manajemen. Ia menulis panduan dan artikel di GudangCode untuk membantu developer Indonesia menjalankan, memahami, dan men-deploy source code Laravel dengan benar.

Laravel PHP MySQL Sistem Informasi Bisnis
Lihat semua artikel Yudhi
Validasi Form Laravel Programming & Laravel
Bacaan Paling Dicari
📚 Free Learning Hub

Learn Coding for Free at DhieCoderWeb

Explore Laravel, PHP, JavaScript tutorials, source code, web development guides, and practical programming tips.

DhieCoderWeb
100+
Tutorials
Free
Learning
SEO
Tips
Visit Dhiecoderweb.com →