BAB 32: Hapus Data
Menghapus record dari database dengan konfirmasi dan feedback yang tepat di Laravel.
Fitur edit yang selesai di bab sebelumnya membuat data catatan bisa dikoreksi kapan saja. Tapi ada satu skenario yang belum ditangani: bagaimana jika catatan memang sudah tidak diperlukan sama sekali? Aplikasi yang tidak punya fitur hapus memaksa pengguna menumpuk data usang yang tidak lagi relevan.
Menambahkan fitur hapus di Laravel mengikuti pola yang sama dengan PUT di fitur edit — browser tidak mendukung HTTP method DELETE secara langsung dari form HTML, sehingga Laravel menggunakan teknik method spoofing yang serupa. Satu route baru, satu method di controller, dan beberapa baris di view sudah cukup untuk melengkapi kemampuan CRUD aplikasi ini.
Route DELETE
Tambahkan satu route terakhir ke routes/web.php:
// routes/web.php
<?php
use App\Http\Controllers\CatatanController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::get('/catatan', [CatatanController::class, 'index'])->name('catatan.index');
Route::get('/catatan/create', [CatatanController::class, 'create'])->name('catatan.create');
Route::post('/catatan', [CatatanController::class, 'store'])->name('catatan.store');
Route::get('/catatan/{catatan}', [CatatanController::class, 'show'])->name('catatan.show');
Route::get('/catatan/{catatan}/edit', [CatatanController::class, 'edit'])->name('catatan.edit');
Route::put('/catatan/{catatan}', [CatatanController::class, 'update'])->name('catatan.update');
Route::delete('/catatan/{catatan}', [CatatanController::class, 'destroy'])->name('catatan.destroy');
Route ini menggunakan HTTP method DELETE dan URL yang sama dengan route show dan update — /catatan/{catatan}. Laravel membedakan ketiganya berdasarkan HTTP method yang digunakan, bukan URL-nya. Ini adalah prinsip REST: satu URL merepresentasikan satu resource, dan method HTTP menentukan operasi yang dilakukan.
Method destroy di Controller
Tambahkan method destroy ke app/Http/Controllers/CatatanController.php:
// app/Http/Controllers/CatatanController.php
<?php
namespace App\Http\Controllers;
use App\Models\Catatan;
use Illuminate\Http\Request;
class CatatanController extends Controller
{
public function index()
{
$catatans = Catatan::latest()->get();
return view('catatan.index', compact('catatans'));
}
public function create()
{
return view('catatan.create');
}
public function store(Request $request)
{
$validated = $request->validate([
'judul' => 'required|string|max:255',
'isi' => 'required|string',
'prioritas' => 'required|in:rendah,sedang,tinggi',
]);
Catatan::create($validated);
return redirect()->route('catatan.index')
->with('success', 'Catatan berhasil disimpan.');
}
public function show(Catatan $catatan)
{
return view('catatan.show', compact('catatan'));
}
public function edit(Catatan $catatan)
{
return view('catatan.edit', compact('catatan'));
}
public function update(Request $request, Catatan $catatan)
{
$validated = $request->validate([
'judul' => 'required|string|max:255',
'isi' => 'required|string',
'prioritas' => 'required|in:rendah,sedang,tinggi',
]);
$catatan->update($validated);
return redirect()->route('catatan.show', $catatan)
->with('success', 'Catatan berhasil diperbarui.');
}
public function destroy(Catatan $catatan)
{
$catatan->delete();
return redirect()->route('catatan.index')
->with('success', 'Catatan berhasil dihapus.');
}
}
Method destroy adalah yang paling sederhana di antara semua method yang sudah ditulis. Tidak ada validasi input, tidak ada logika kondisional — cukup $catatan->delete() dan redirect ke halaman listing.
Ini bisa singkat karena route model binding sudah menangani kerumitannya: saat request masuk dengan URL /catatan/5, Laravel secara otomatis mencari record dengan id = 5 di tabel catatans dan menyuntikkannya ke parameter $catatan. Jika tidak ditemukan, Laravel melempar 404 sebelum destroy sempat dieksekusi. Jadi di dalam method, objek $catatan sudah pasti valid dan sudah siap dihapus.
$catatan->delete() menjalankan DELETE FROM catatans WHERE id = 5 di database, lalu mengarahkan pengguna ke halaman daftar dengan pesan konfirmasi.
Operasi delete() permanen dan tidak bisa dibatalkan. Data yang sudah dihapus tidak bisa dikembalikan kecuali ada backup atau kamu mengimplementasikan soft delete. Selalu pastikan ada konfirmasi sebelum pengguna bisa menghapus data.
Tombol Hapus di Halaman Detail
Tombol hapus ditempatkan di halaman detail, berdampingan dengan tombol Edit. Perbarui resources/views/catatan/show.blade.php:
{{-- resources/views/catatan/show.blade.php --}}
@extends('layouts.app')
@section('title', $catatan->judul)
@section('content')
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-bold text-gray-900">{{ $catatan->judul }}</h1>
<div class="flex items-center gap-3">
<a href="{{ route('catatan.edit', $catatan) }}" class="bg-yellow-500 text-white px-3 py-1.5 rounded text-sm font-medium hover:bg-yellow-600">
Edit
</a>
<form action="{{ route('catatan.destroy', $catatan) }}" method="POST"
onsubmit="return confirm('Yakin ingin menghapus catatan ini?')">
@csrf
@method('DELETE')
<button type="submit" class="bg-red-600 text-white px-3 py-1.5 rounded text-sm font-medium hover:bg-red-700">
Hapus
</button>
</form>
<a href="{{ route('catatan.index') }}" class="text-sm text-gray-500 hover:text-gray-700">
Kembali ke daftar
</a>
</div>
</div>
<div class="bg-white rounded border border-gray-200 p-6">
<div class="mb-4">
<span class="inline-block bg-gray-100 text-gray-700 text-xs font-medium px-2 py-1 rounded capitalize">
Prioritas: {{ $catatan->prioritas }}
</span>
<span class="ml-2 text-xs text-gray-400">
{{ $catatan->created_at->format('d M Y, H:i') }}
</span>
</div>
<div class="text-gray-700 text-sm leading-relaxed whitespace-pre-wrap">
{{ $catatan->isi }}
</div>
</div>
@endsection
Ada beberapa hal yang perlu diperhatikan di bagian ini.
Tombol hapus harus berupa <form>, bukan <a>. Link biasa hanya bisa mengirim request GET. Karena route destroy menggunakan method DELETE, kita butuh form dengan @method('DELETE') — teknik yang sama seperti @method('PUT') di form edit.
@method('DELETE') menghasilkan <input type="hidden" name="_method" value="DELETE">. Laravel membaca hidden input ini dan memperlakukan request seolah-olah methodnya DELETE, meskipun browser mengirimnya sebagai POST.
onsubmit="return confirm(...) adalah konfirmasi sederhana menggunakan dialog bawaan browser. Jika pengguna menekan “OK”, form dikirim. Jika menekan “Batal”, form tidak jadi dikirim. Ini perlindungan minimal yang mencegah penghapusan tidak sengaja karena klik yang salah.
Untuk konfirmasi yang lebih baik dari tampilan visual, kamu bisa mengganti confirm() dengan modal dialog menggunakan JavaScript. Tapi untuk aplikasi sederhana, confirm() sudah cukup fungsional dan tidak perlu library tambahan.
Menguji Fitur Hapus
Buka halaman detail catatan mana saja. Sekarang ada tiga aksi di header — tombol Edit kuning, tombol Hapus merah, dan link “Kembali ke daftar”.
Klik tombol Hapus merah. Browser menampilkan dialog konfirmasi dengan pesan “Yakin ingin menghapus catatan ini?”.
Klik “OK”. Laravel memproses request DELETE, memanggil $catatan->delete(), lalu mengarahkan ke halaman listing dengan flash message. Catatan yang baru saja dihapus tidak akan muncul lagi di daftar.
CRUD Selesai
Dengan selesainya fitur hapus, aplikasi manajemen catatan sudah memiliki kemampuan CRUD yang lengkap:
- Create — form di
/catatan/create, diproses olehstore() - Read — listing di
/catatan, detail di/catatan/{id} - Update — form di
/catatan/{id}/edit, diproses olehupdate() - Delete — tombol di halaman detail, diproses oleh
destroy()
Empat operasi ini adalah fondasi dari hampir semua aplikasi web berbasis data. Pola yang sudah dipakai sepanjang bab-bab studi kasus ini — route, controller, view, model binding, validasi, flash message — akan terus berulang di fitur-fitur berikutnya dengan variasi yang semakin kompleks.
Tapi ada satu masalah yang langsung terasa setelah fitur hapus ini diuji: setelah menghapus beberapa catatan, database jadi kosong lagi. Mengisi ulang data lewat form satu per satu membuang waktu. Di bab berikutnya, kita akan menyelesaikan masalah ini dengan seeder dan factory — dua alat yang membuat database bisa terisi otomatis dengan data realistis hanya dari satu perintah.