Sebagian data tidak cukup diperiksa sendirian — keabsahannya ditentukan oleh isi database. Dua alamat email yang sama tidak boleh terdaftar, dan role_id yang dikirim pengguna memang harus ada di tabel roles. Untuk kebutuhan ini Laravel menyediakan dua rule yang menjalankan query database di balik layar: unique memastikan nilai belum pernah dipakai, dan exists memastikan nilai benar-benar tersedia. Keduanya punya jebakan klasik yang akan kita bedah — terutama saat memperbarui data milik sendiri.
unique:table,column untuk Data yang Harus Baru #
Bentuk string-nya adalah unique:tabel,kolom — Laravel memeriksa apakah nilai tersebut sudah ada di kolom itu:
$validated = $request->validate([
'email' => ['required', 'email', 'unique:users,email'],
]);Email yang sudah terdaftar akan ditolak. Sebagai panggung uji, kita siapkan tabel roles dengan kolom nama bertipe unik dan soft delete (deleted_at), plus satu user [email protected]. Feature test berikut mencoba memakai email milik orang lain:
$r = $this->postJson('/profil/2', [
'name' => 'Siti',
'email' => '[email protected]',
'role_id' => 1,
]);Output yang Dihasilkan:
email duplikat user lain:
{"message":"The email has already been taken. (and 1 more error)","errors":{"email":["The email has already been taken."],"role_id":["The selected role id is invalid."]}}Dua error muncul sekaligus: email sudah dipakai, dan role_id bernilai 1 tidak ditemukan di tabel roles yang masih kosong pada test tersebut — contoh kecil rule exists bekerja dalam satu tarikan.
exists:table,column untuk Data yang Harus Ada #
Kebalikan unique, rule exists menuntut nilai benar-benar hadir di database:
'role_id' => ['required', 'integer', Rule::exists('roles', 'id')],Saat kita isi tabel roles dengan satu baris lalu mengirim role_id 999:
Output yang Dihasilkan:
role_id 999 tidak ada:
{"message":"The selected role id is invalid.","errors":{"role_id":["The selected role id is invalid."]}}Jebakan Update: Rule::unique()->ignore() #
Masalah klasik unique muncul di halaman edit profil. Pengguna yang hanya mengubah nama tetap mengirim email lamanya — dan unique:users,email menolaknya karena email itu memang sudah ada di database, milik dia sendiri! Solusinya adalah bentuk objek Rule::unique() dengan ignore():
public function update(Request $request, int $id)
{
$validated = $request->validate([
'name' => ['required', 'string'],
'email' => [
'required',
'email',
Rule::unique('users', 'email')->ignore($id),
],
'role_id' => [
'required',
'integer',
Rule::exists('roles', 'id'),
],
]);
return response()->json($validated);
}Method ignore($id) memberi tahu Laravel: baris dengan ID tersebut dikecualikan dari query pengecekan. Karena route menangkap {id} user yang sedang diedit, email miliknya sendiri tidak dihitung sebagai duplikat:
Output yang Dihasilkan:
email sendiri di-update:
{"name":"Budi Santoso","email":"[email protected]","role_id":1}Email yang sama kini lolos validasi, sedangkan email milik user lain tetap ditolak seperti sebelumnya.
Soft Delete dan Kondisi Tambahan #
Query unique bawaan tidak tahu apa-apa tentang soft delete — baris yang sudah “dihapus” (kolom deleted_at terisi) tetap dihitung ada. Kita buktikan pada tabel roles yang punya baris penulis ter-soft-delete:
Output yang Dihasilkan:
unique polos + nama ter-soft-delete: GAGAL
unique + whereNull(deleted_at): LOLOS
whereNull vs nama aktif 'editor' : GAGAL
error: The nama has already been taken.Baris pertama menunjukkan masalahnya: nama yang sudah di-soft-delete tetap membatasi pengguna baru. Perbaikannya adalah menambahkan kondisi query lewat whereNull:
'nama' => [
'required',
Rule::unique('roles', 'nama')->whereNull('deleted_at'),
],Baris kedua dan ketiga membuktikan hasilnya: nama ter-soft-delete kini bebas dipakai ulang, sementara nama yang masih aktif (editor) tetap ditolak dengan pesan The nama has already been taken.. Method where() juga tersedia untuk kondisi lain, misalnya ->where('team_id', $team->id) saat uniqueness hanya berlaku dalam lingkup satu tim.
Satu catatan terakhir soal kapitalisasi: pada database SQLite dan MySQL dengan collation bawaan, unique bersifat case-insensitive sehingga [email protected] dan [email protected] dianggap duplikat. PostgreSQL justru case-sensitive secara bawaan — jika butuh konsistensi, pertimbangkan Rule::unique(...)->whereRaw('lower(email) = ?', [strtolower($value)]) atau normalisasi email di prepareForValidation().
Validasi unique dan exists menuntaskan sisi logika data. Berikutnya kita beralih ke sisi pengalaman pengguna: menampilkan pesan-pesan error tersebut di halaman form dengan directive Blade khusus. Dokumentasi resminya ada di dokumentasi Validation — unique & exists
.