Tutorial Laravel #30: Mini Project CRUD Laravel
1. Tujuan Mini Project
Di tutorial terakhir ini, kita akan membangun aplikasi blog sederhana yang menggabungkan semua materi dari 29 tutorial sebelumnya. Project ini mencakup authentication, CRUD artikel, upload gambar, kategori, validasi, dan pagination - semua menggunakan cara-cara Laravel yang proper.
2. Fitur Aplikasi
- Register dan login (via Laravel Breeze)
- Dashboard setelah login
- Manajemen kategori (CRUD)
- Manajemen artikel (CRUD + upload gambar)
- Halaman publik dengan artikel published
- Pagination dan search
- Authorization (hanya bisa edit/hapus artikel sendiri)
3. Setup Project
# Buat project baru
laravel new blog-mini
cd blog-mini
# Install Breeze untuk auth
composer require laravel/breeze --dev
php artisan breeze:install blade
npm install && npm run dev
# Konfigurasi database di .env lalu jalankan migration
php artisan migrate
# Buat model, migration, dan controller sekaligus
php artisan make:model Kategori -m
php artisan make:model Artikel -m
php artisan make:controller Admin/ArtikelController --resource --model=Artikel
php artisan make:controller Admin/KategoriController --resource --model=Kategori
php artisan make:controller HomeController
php artisan storage:link
4. Migration Database
// Migration kategoris
Schema::create('kategoris', function (Blueprint $table) {
$table->id();
$table->string('nama', 100)->unique();
$table->string('slug')->unique();
$table->timestamps();
});
// Migration artikels
Schema::create('artikels', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->foreignId('kategori_id')->nullable()->constrained()->nullOnDelete();
$table->string('judul');
$table->string('slug')->unique();
$table->text('ringkasan')->nullable();
$table->longText('konten');
$table->string('gambar')->nullable();
$table->enum('status', ['draft', 'published'])->default('draft');
$table->integer('views')->default(0);
$table->timestamps();
});
php artisan migrate
5. Model dengan Relasi dan Observer
<?php
// app/Models/Artikel.php
namespace AppModels;
use IlluminateDatabaseEloquentModel;
use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateSupportStr;
class Artikel extends Model
{
use HasFactory;
protected $fillable = [
'user_id', 'kategori_id', 'judul', 'slug',
'ringkasan', 'konten', 'gambar', 'status',
];
// Auto-generate slug saat judul diisi
protected static function booted(): void
{
static::creating(function ($artikel) {
$artikel->slug = Str::slug($artikel->judul);
});
}
public function user() { return $this->belongsTo(User::class); }
public function kategori() { return $this->belongsTo(Kategori::class); }
public function scopePublished($query) {
return $query->where('status', 'published');
}
}
6. Route
// routes/web.php
use AppHttpControllersHomeController;
use AppHttpControllersAdminArtikelController;
use AppHttpControllersAdminKategoriController;
// Public
Route::get('/', [HomeController::class, 'index'])->name('home');
Route::get('/artikel/{artikel:slug}', [HomeController::class, 'show'])->name('artikel.show');
// Admin (butuh login)
Route::middleware(['auth', 'verified'])->prefix('admin')->name('admin.')->group(function () {
Route::get('/dashboard', fn() => view('admin.dashboard'))->name('dashboard');
Route::resource('artikel', ArtikelController::class);
Route::resource('kategori', KategoriController::class);
});
7. Admin Artikel Controller
<?php
namespace AppHttpControllersAdmin;
use AppHttpControllersController;
use AppModelsArtikel;
use AppModelsKategori;
use IlluminateHttpRequest;
use IlluminateSupportFacadesStorage;
class ArtikelController extends Controller
{
public function index(Request $request)
{
$query = Artikel::with('kategori')->where('user_id', auth()->id());
if ($request->filled('cari')) {
$query->where('judul', 'like', '%' . $request->cari . '%');
}
$artikels = $query->latest()->paginate(10)->withQueryString();
return view('admin.artikel.index', compact('artikels'));
}
public function create()
{
$kategoris = Kategori::orderBy('nama')->get();
return view('admin.artikel.create', compact('kategoris'));
}
public function store(Request $request)
{
$data = $request->validate([
'judul' => 'required|string|max:200',
'kategori_id' => 'nullable|exists:kategoris,id',
'ringkasan' => 'nullable|string|max:500',
'konten' => 'required|string',
'gambar' => 'nullable|image|mimes:jpg,jpeg,png,webp|max:2048',
'status' => 'required|in:draft,published',
]);
if ($request->hasFile('gambar')) {
$data['gambar'] = $request->file('gambar')->store('artikel', 'public');
}
auth()->user()->artikels()->create($data);
return redirect()->route('admin.artikel.index')
->with('success', 'Artikel berhasil disimpan!');
}
public function edit(Artikel $artikel)
{
$this->authorize('update', $artikel);
$kategoris = Kategori::orderBy('nama')->get();
return view('admin.artikel.edit', compact('artikel', 'kategoris'));
}
public function update(Request $request, Artikel $artikel)
{
$this->authorize('update', $artikel);
$data = $request->validate([
'judul' => 'required|string|max:200',
'kategori_id' => 'nullable|exists:kategoris,id',
'ringkasan' => 'nullable|string|max:500',
'konten' => 'required|string',
'gambar' => 'nullable|image|mimes:jpg,jpeg,png,webp|max:2048',
'status' => 'required|in:draft,published',
]);
if ($request->hasFile('gambar')) {
if ($artikel->gambar) Storage::disk('public')->delete($artikel->gambar);
$data['gambar'] = $request->file('gambar')->store('artikel', 'public');
}
$artikel->update($data);
return redirect()->route('admin.artikel.index')
->with('success', 'Artikel berhasil diperbarui!');
}
public function destroy(Artikel $artikel)
{
$this->authorize('delete', $artikel);
if ($artikel->gambar) Storage::disk('public')->delete($artikel->gambar);
$artikel->delete();
return redirect()->route('admin.artikel.index')
->with('success', 'Artikel berhasil dihapus!');
}
}
8. Halaman Publik - Home Controller
<?php
namespace AppHttpControllers;
use AppModelsArtikel;
class HomeController extends Controller
{
public function index()
{
$artikels = Artikel::with(['user', 'kategori'])
->published()
->latest()
->paginate(9);
return view('home', compact('artikels'));
}
public function show(Artikel $artikel)
{
abort_if($artikel->status !== 'published', 404);
$artikel->increment('views');
$related = Artikel::published()
->where('kategori_id', $artikel->kategori_id)
->where('id', '!=', $artikel->id)
->take(3)->get();
return view('artikel-show', compact('artikel', 'related'));
}
}
9. Materi yang Dipakai di Project Ini
- Routing: Route::resource, prefix, middleware, named route
- Controller: resource controller, route model binding, authorization
- View: Blade layout, section, component, @forelse, @error
- Migration dan Model: Eloquent, relasi belongsTo/hasMany, scope, observer
- Validasi: Form validation, pesan kustom
- Upload file: Storage, symlink, hapus file lama
- Session: Flash message untuk notifikasi
- Authentication: Laravel Breeze
- Authorization: Policy untuk edit/delete
- Pagination: paginate() dengan filter dan withQueryString()
10. Langkah Selanjutnya
Setelah selesai dengan mini project ini, kamu bisa kembangkan lebih lanjut:
- Tambah fitur komentar dengan relasi polymorphic
- Implementasikan tag dengan relasi many-to-many
- Tambah fitur like/bookmark artikel
- Buat REST API untuk mobile app menggunakan Sanctum
- Implementasikan queue untuk email notifikasi
- Tambah fitur pencarian full-text dengan Laravel Scout
- Deploy ke VPS menggunakan Laravel Forge atau manual
Selamat! Kamu telah menyelesaikan seluruh seri tutorial Laravel. Dengan fondasi yang kuat ini, kamu siap untuk membangun aplikasi web yang nyata. Selamat berkarya!
.jpg)
