Inventaris Lab Laravel 12 : Bagian #05 - MEMBUAT HALAMAN CRUD USERS
Belajar Bareng Minarsih - Edisi Ngoding
09 Oktober 2025
Tujuan: Selamat! Anda telah berhasil membangun fondasi dan halaman dashboard. Di bagian ini, kita akan membangun fitur inti pertama kita: halaman untuk mengelola data pengguna (Users). Kita akan mempelajari cara Laravel mempercepat pembuatan fitur CRUD menggunakan Resource Controller dan Resource Route.
Kita akan mulai dari "belakang", yaitu menyiapkan rute dan controller terlebih dahulu.
php artisan make:controller UserController --resource
Penjelasan: Perintah ini membuat file UserController.php. Opsi --resource secara ajaib akan membuatkan semua fungsi yang kita butuhkan untuk CRUD (index, create, store, show, edit, update, destroy) di dalam controller tersebut.
use App\Http\Controllers\UserController;
Route::resource('users', UserController::class);
php artisan route:list --name=users
Sekarang kita akan mengisi logika untuk menampilkan daftar pengguna.
use App\Models\User;
class UserController extends Controller
{
// Variabel untuk data umum
protected $title = 'Users';
protected $menu = 'users';
protected $directory = 'admin.users'; // Diubah ke folder view users
]
public function index()
{
// Menyiapkan array untuk dikirim ke view
$data['title'] = $this->title;
$data['menu'] = $this->menu;
// Mengambil data dari database
$data['users'] = User::where('role', 'Siswa')->latest()->get();
// Me-return view beserta data
return view($this->directory . '.index', $data);
}
Penjelasan Kode: Baris $data['users'] = User::where('role', 'Siswa')->latest()->get(); berarti:
Langkah 3: Menyiapkan File-File View
Kita siapkan "kanvas" untuk halaman-halaman CRUD kita.
Mari kita fokus mengisi file index.blade.php.
@extends('admin.layouts.app')
@section('css')
{{-- CSS Tambahan --}}
@endsection
@section('content')
{{-- Konten Utama --}}
@endsection
@section('js')
{{-- JS Tambahan --}}
@endsection
@section('content')
<div class="card">
<div class="card-body">
<h5 class="card-title fw-semibold mb-4">Data {{ $title }}</h5>
<a href="{{ route('users.create') }}" class="btn btn-primary mb-4">Tambah Data {{ $title }}</a>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>No</th>
<th>Nama</th>
<th>Email</th>
<th>Role</th>
<th>Aksi</th>
</tr>
</thead>
<tbody>
@foreach ($users as $item)
<tr>
<td>{{ $loop->iteration }}</td>
<td>{{ $item->name }}</td>
<td>{{ $item->email }}</td>
<td>{{ $item->role }}</td>
<td>
<a href="{{ route('users.edit', $item->id) }}" class="btn btn-warning btn-sm">Ubah</a>
<form action="{{ route('users.destroy', $item->id) }}" method="POST" class="d-inline">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('Apakah Anda ingin menghapusnya?')">Hapus</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
@endsection
Penjelasan Tabel:
Agar halaman Users bisa diakses, kita perlu menambahkan link menunya di sidebar.
<ul id="sidebarnav">
{{-- MENU HOME --}}
<li class="nav-small-cap">
<i class="ti ti-dots nav-small-cap-icon fs-4"></i>
<span class="hide-menu">Home</span>
</li>
<li class="sidebar-item {{ Request::is('dashboard') ? 'selected' : '' }}">
<a class="sidebar-link {{ Request::is('dashboard') ? 'active' : '' }}" href="{{ route('dashboard') }}" aria-expanded="false">
<span>
<i class="ti ti-layout-dashboard"></i>
</span>
<span class="hide-menu">Dashboard</span>
</a>
</li>
{{-- MENU DATAMASTER --}}
<li class="nav-small-cap">
<i class="ti ti-dots nav-small-cap-icon fs-4"></i>
<span class="hide-menu">Datamaster</span>
</li>
<li class="sidebar-item {{ Request::is('users*') ? 'selected' : '' }}">
<a class="sidebar-link {{ Request::is('users*') ? 'active' : '' }}" href="{{ route('users.index') }}" aria-expanded="false">
<span>
<i class="ti ti-users"></i>
</span>
<span class="hide-menu">Data Users</span>
</a>
</li>
</ul>
Perbaikan Penting: Perhatikan kita menggunakan route('users.index') karena ini nama rute untuk halaman daftar user. Kita juga menggunakan Request::is('users*') (dengan tanda bintang *) agar menu tetap aktif meskipun kita berada di halaman tambah atau ubah user (misal: /users/create atau /users/1/edit).
Langkah 7: Mempercantik Tabel dengan DataTables
Tabel HTML standar terlihat sangat biasa. Kita akan mengubahnya menjadi tabel interaktif dengan fitur pencarian, sortir, dan paginasi menggunakan library JavaScript bernama DataTables.
<link rel="stylesheet" href="https://cdn.datatables.net/2.3.4/css/dataTables.dataTables.css" />
<script src="https://cdn.datatables.net/2.3.4/js/dataTables.js"></script>
<table id="datatable" class="table table-striped">
@section('js')
<script>
$(document).ready( function () {
$('#datatable').DataTable();
} );
</script>
@endsection
Peringatan confirm() bawaan JavaScript terlihat kuno. Kita akan menggantinya dengan SweetAlert yang lebih elegan, sekaligus menyiapkan sistem notifikasi untuk nanti.
<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
<script>
@if (session('status'))
swal({
title: '{{ session('title') }}',
text: '{{ session('message') }}',
icon: '{{ session('status') }}',
});
@endif
</script>
PHP
<form action="{{ route('users.destroy', $item->id) }}" method="POST" class="d-inline">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('Apakah Anda ingin menghapusnya ?')">Hapus</button>
</form>
<form id="deleteForm{{ $item->id }}" action="{{ route('users.destroy', $item->id) }}" method="POST" class="d-inline">
@csrf
@method('DELETE')
<button type="button" class="btn btn-danger btn-sm" onclick="confirmDelete({{ $item->id }})">Hapus</button>
</form>
@section('js')
<script>
// Script untuk DataTables
$(document).ready( function () {
$('#datatable').DataTable();
} );
// Script untuk SweetAlert
function confirmDelete(id) {
swal({
title: "Apakah anda yakin?",
text: "Data yang dihapus tidak dapat dikembalikan!",
icon: "warning",
buttons: true,
dangerMode: true,
})
.then((willDelete) => {
if (willDelete) {
// Jika pengguna menekan "OK", submit form
$('#deleteForm' + id).submit();
} else {
// Jika pengguna menekan "Cancel"
swal("Data tidak jadi dihapus!", {
icon: "error",
});
}
});
}
</script>
@endsection
Sekarang kita akan membuat fungsionalitas untuk menambah data user baru. Kita akan mulai dari menyiapkan controller, mendesain view (tampilan), hingga menulis logika untuk menyimpan data ke database.
public function create()
{
// Menyiapkan array untuk dikirim ke view
$data['title'] = $this->title;
$data['menu'] = $this->menu;
// Me-return view beserta data
return view($this->directory . '.create', $data);
}
@extends('admin.layouts.app')
@section('css')
{{-- CSS Tambahan --}}
@endsection
@section('content')
{{-- Konten Utama akan ditaruh di sini --}}
@endsection
@section('js')
{{-- JS Tambahan --}}
@endsection
{{-- Di dalam @section('content') --}}
<div class="card">
<div class="card-body">
<h5 class="card-title fw-semibold mb-4">Tambah Data {{ $title }}</h5>
<div class="card">
<div class="card-body">
<form action="{{ route('users.store') }}" method="POST">
{{-- Input form akan kita letakkan di sini --}}
</form>
</div>
</div>
</div>
</div>
<form action="{{ route('users.store') }}" method="POST">
@csrf
{{-- Nama --}}
<div class="mb-3">
<label for="name" class="form-label">Nama</label>
<input type="text" class="form-control @error('name') is-invalid @enderror" name="name" id="name" placeholder="Nama Lengkap" value="{{ old('name') }}">
@error('name')
<small class="text-danger">{{ $message }}</small>
@enderror
</div>
{{-- Email --}}
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control @error('email') is-invalid @enderror" name="email" id="email" placeholder="contoh@email.com" value="{{ old('email') }}">
@error('email')
<small class="text-danger">{{ $message }}</small>
@enderror
</div>
{{-- Password --}}
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control @error('password') is-invalid @enderror" name="password" id="password" placeholder="Password">
@error('password')
<small class="text-danger">{{ $message }}</small>
@enderror
</div>
{{-- Role --}}
<div class="mb-3">
<label for="role" class="form-label">Role</label>
<select class="form-select @error('role') is-invalid @enderror" name="role" id="role">
<option value="Siswa" {{ old('role') == 'Siswa' ? 'selected' : '' }}>Siswa</option>
</select>
@error('role')
<small class="text-danger">{{ $message }}</small>
@enderror
</div>
{{-- Tombol --}}
<button type="submit" class="btn btn-primary">Simpan</button>
<a href="{{ route('users.index') }}" class="btn btn-warning">Kembali</a>
</form>
Penjelasan Singkat Kode Form:
Sebelum menyimpan data, kita perlu melakukan dua hal: mengizinkan kolom role untuk diisi dan menulis logika penyimpanan di controller.
public function store(Request $request)
{
// 1. Validasi data
$validatedData = $request->validate([
'name' => 'required|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:5',
'role' => 'required'
]);
// 2. Enkripsi password
$validatedData['password'] = Hash::make($validatedData['password']);
// 3. Simpan data ke database
$user = User::create($validatedData);
// 4. Redirect dengan pesan sukses
if ($user) {
return redirect()->route('users.index')->with([
'status' => 'success',
'title' => 'Berhasil',
'message' => 'Data Berhasil Ditambahkan!'
]);
} else {
return redirect()->route('users.index')->with([
'status' => 'danger',
'title' => 'Gagal',
'message' => 'Data Gagal Ditambahkan!'
]);
}
}
Penjelasan Kode store():
Kita akan mulai dengan menyiapkan data di controller dan membuat tampilan untuk form ubah data.
public function edit(string $id)
{
// Menyiapkan array untuk dikirim ke view
$data['title'] = $this->title;
$data['menu'] = $this->menu;
// Mencari data user berdasarkan ID
$data['user'] = User::findOrFail($id);
// Me-return view beserta data
return view($this->directory . '.edit', $data);
}
Penjelasan Singkat: Metode findOrFail($id) sama seperti find($id), namun jika ID user tidak ditemukan di database, ia akan otomatis menampilkan halaman '404 Not Found'. Ini adalah praktik yang lebih baik dan lebih aman daripada membiarkan aplikasi error.
<form action="{{ route('users.update', $user->id) }}" method="POST">
@csrf
@method('PUT')
<input type="text" ... value="{{ old('name', $user->name) }}">
<input type="email" ... value="{{ old('email', $user->email) }}">
<input type="password" ... placeholder="Kosongkan jika tidak ingin diubah">
<select ...>
<option value="Siswa" {{ old('role', $user->role) == 'Siswa' ? 'selected' : '' }}>Siswa</option>
</select>
@extends('admin.layouts.app')
@section('css')
{{-- CSS Tambahan --}}
@endsection
@section('content')
<div class="card">
<div class="card-body">
<h5 class="card-title fw-semibold mb-4">Ubah Data {{ $title }}</h5>
<div class="card">
<div class="card-body">
<form action="{{ route('users.update', $user->id) }}" method="POST">
@csrf
@method('PUT')
{{-- Nama --}}
<div class="mb-3">
<label for="name" class="form-label">Nama</label>
<input type="text" class="form-control @error('name') is-invalid @enderror" name="name" id="name" placeholder="Nama Lengkap" value="{{ old('name', $user->name) }}">
@error('name')
<small class="text-danger">{{ $message }}</small>
@enderror
</div>
{{-- Email --}}
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control @error('email') is-invalid @enderror" name="email" id="email" placeholder="contoh@email.com" value="{{ old('email', $user->email) }}">
@error('email')
<small class="text-danger">{{ $message }}</small>
@enderror
</div>
{{-- Password --}}
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control @error('password') is-invalid @enderror" name="password" id="password" placeholder="Kosongkan jika tidak ingin diubah">
@error('password')
<small class="text-danger">{{ $message }}</small>
@enderror
</div>
{{-- Role --}}
<div class="mb-3">
<label for="role" class="form-label">Role</label>
<select class="form-select @error('role') is-invalid @enderror" name="role" id="role">
<option value="Siswa" {{ old('role', $user->role) == 'Siswa' ? 'selected' : '' }}>Siswa</option>
</select>
@error('role')
<small class="text-danger">{{ $message }}</small>
@enderror
</div>
{{-- Tombol --}}
<button type="submit" class="btn btn-primary">Simpan</button>
<a href="{{ route('users.index') }}" class="btn btn-warning">Kembali</a>
</form>
</div>
</div>
</div>
</div>
@endsection
@section('js')
{{-- JS Tambahan --}}
@endsection
Sekarang kita tulis logika untuk memproses data yang dikirim dari form ubah.
public function update(Request $request, string $id)
{
// 1. Cari data user berdasarkan ID
$user = User::findOrFail($id);
// 2. Validasi data
$validatedData = $request->validate([
'name' => 'required|max:255',
'email' => 'required|email|unique:users,email,' . $id,
'password' => 'nullable|min:5', // Password boleh kosong
'role' => 'required'
]);
// 3. Menyiapkan data untuk diupdate
$updateData = [
'name' => $validatedData['name'],
'email' => $validatedData['email'],
'role' => $validatedData['role'],
];
// 4. Jika password diisi, enkripsi dan tambahkan ke data update
if ($request->filled('password')) {
$updateData['password'] = Hash::make($validatedData['password']);
}
// 5. Update data di database
$updateProcess = $user->update($updateData);
// 6. Redirect dengan pesan sukses
if ($updateProcess) {
return redirect()->route('users.index')->with([
'status' => 'success',
'title' => 'Berhasil',
'message' => 'Data Berhasil Diubah!'
]);
} else {
return redirect()->route('users.index')->with([
'status' => 'danger',
'title' => 'Gagal',
'message' => 'Data Gagal Diubah!'
]);
}
}
Langkah 15: Menulis Logika Hapus (Destroy) di Controller
Sekarang kita akan menulis logika untuk memproses permintaan hapus data yang dikirim dari tombol "Hapus" di halaman index.
public function destroy(string $id)
{
// 1. Cari data user berdasarkan ID
$user = User::findOrFail($id);
// 2. Lakukan proses delete
if ($user) {
$user->delete();
// 3. Jika berhasil, redirect dengan pesan sukses
return redirect()->route('users.index')->with([
'status' => 'success',
'title' => 'Berhasil',
'message' => 'Data Berhasil Dihapus!'
]);
} else {
// 4. Jika gagal, redirect dengan pesan gagal
return redirect()->route('users.index')->with([
'status' => 'danger',
'title' => 'Gagal',
'message' => 'Data Gagal Dihapus!'
]);
}
}
Penjelasan Singkat Kode destroy():
Saatnya memastikan semua bagian (Tombol -> SweetAlert -> Controller -> Redirect) bekerja dengan sempurna.
Selamat! Anda telah berhasil membangun fungsionalitas CRUD (Create, Read, Update, Delete) yang lengkap untuk halaman Users. Ini adalah salah satu keterampilan paling fundamental dan penting dalam pengembangan web.
Artikel Lainnya Dengan Kategori Terkait :
1. Inventaris Lab Laravel 12 : Bagian #01 - INSTALASI LARAGON, PHP, PHPMYADMIN & LARAVEL
2. Inventaris Lab Laravel 12 : Bagian #02 - KONFIGURASI DATABASE & FONDASI PROYEK
3. Inventaris Lab Laravel 12 : Bagian #03 - MEMBANGUN HALAMAN ADMIN DENGAN BLADE TEMPLATING
4. Inventaris Lab Laravel 12 : Bagian #04 - MEMBUAT HALAMAN DASHBOARD DINAMIS
5. Inventaris Lab Laravel 12 : Bagian #05 - MEMBUAT HALAMAN CRUD USERS
6. Inventaris Lab Laravel 12 : Bagian #06 - MEMBUAT FITUR AUTENTIKASI (LOGIN) & PENYESUAIAN UI
7. Inventaris Lab Laravel 12 : Bagian #07 - MEMBUAT HALAMAN CRUD CATEGORY
8. Inventaris Lab Laravel 12 : Bagian #08 - MEMBUAT HALAMAN CRUD ITEM
9. Inventaris Lab Laravel 12 : Bagian #09 - MEMBUAT HALAMAN TRANSAKSI PEMINJAMAN (LOAN)
10. Inventaris Lab Laravel 12 : Bagian #10 - HAK AKSES (AUTHORIZATION) & HALAMAN SISWA
Mahardika Oktadiansyah - 15 Juli 2025
Belajar CSS Lanjutan #395 | CSS padding-inline Property
Mahardika Oktadiansyah - 15 Juli 2025
Belajar CSS Lanjutan #394 | CSS padding-bottom Property
Mahardika Oktadiansyah - 15 Juli 2025
Belajar CSS Lanjutan #393 | CSS padding-block-start Property