Panduan Tutorial Laravel

Belajar Laravel Batch 10: Mini Project Akhir - Membangun Blog Sederhana

Belajar Laravel Batch 10: Mini Project Akhir - Membangun Blog Sederhana
Twitter / X WhatsApp Facebook LinkedIn Telegram
Apa yang kamu butuhkan

Di batch terakhir ini, PahamITian akan mengaplikasikan semua ilmu Laravel yang sudah dipelajari untuk membangun mini blog sederhana. Integrasikan routing, controller, model, migration, Blade, autentikasi, dan upload file menjadi satu proyek utuh.

Halo, PahamITian! Selamat datang di batch terakhir dari seri tutorial Laravel dasar kita! Jika kamu sudah sampai di sini, itu artinya kamu sudah melewati banyak tantangan dan menyerap banyak ilmu. Salut! Sekarang, saatnya kita merangkum semua yang sudah dipelajari dan mengaplikasikannya dalam sebuah proyek nyata: Mini Blog Sederhana.

Batch ini adalah puncak dari perjalananmu. Kita akan menyatukan semua kepingan puzzle yang sudah kita kumpulkan dari Batch 1 hingga Batch 9. Ini bukan hanya tentang membuat fitur, tapi tentang bagaimana semua komponen Laravel bekerja sama secara harmonis untuk membentuk sebuah aplikasi web yang fungsional.

Tujuan Batch 10: Membangun Mini Blog Laravel

Di batch ini, kita akan membangun sebuah aplikasi blog sederhana dengan fitur-fitur utama:

  • Login Admin: Untuk mengelola artikel dan kategori.
  • CRUD Artikel: Membuat, membaca, memperbarui, dan menghapus artikel.
  • CRUD Kategori: Membuat, membaca, memperbarui, dan menghapus kategori.
  • Upload Gambar: Untuk featured image artikel.
  • Status Publish: Artikel bisa berupa draft atau published.
  • Halaman Publik: Menampilkan daftar artikel dan detail artikel yang sudah published.
  • Relasi Data: Artikel terhubung dengan user yang membuatnya dan kategori.

Recap Singkat: Perjalanan Kita Sejauh Ini

Sebelum melangkah lebih jauh, mari kita ingat kembali apa saja yang sudah kita pelajari:

  • Batch 1: Pengenalan Laravel - Apa itu Laravel, struktur folder, alur request.
  • Batch 2: Routing dan Blade - Mengatur URL dan membuat tampilan dinamis.
  • Batch 3: Controller dan Form - Memisahkan logika aplikasi dan menangani input pengguna.
  • Batch 4: Database, Migration, dan Model - Membuat tabel database dan berinteraksi dengan data.
  • Batch 5: CRUD Lengkap - Menguasai operasi dasar data (Create, Read, Update, Delete).
  • Batch 6: Relasi Database Dasar - Menghubungkan antar tabel (One-to-Many).
  • Batch 7: Authentication Dasar - Sistem login dan register untuk mengamankan aplikasi.
  • Batch 8: Upload File dan Storage - Mengelola file yang diunggah pengguna.
  • Batch 9: SEO dan Publish Status - Mengoptimalkan artikel untuk mesin pencari dan mengatur status publikasi.

Sekarang, mari kita satukan semuanya!

Struktur Pengerjaan Langkah demi Langkah: Mini Blog Project

Kita akan melanjutkan dari proyek yang sudah kita bangun di batch-batch sebelumnya. Pastikan kamu sudah memiliki project Laravel yang berfungsi dengan baik.

1. Persiapan Database & Model

Kita akan membutuhkan dua tabel baru: categories dan posts. Tabel users sudah ada dari setup autentikasi.

a. Migrasi untuk categories:

BASH
php artisan make:migration create_categories_table

Edit file migrasi yang baru dibuat (database/migrations/..._create_categories_table.php):

PHP
// ...
public function up(): void
{
    Schema::create('categories', function (Blueprint $table) {
        $table->id();
        $table->string('name')->unique();
        $table->string('slug')->unique();
        $table->timestamps();
    });
}
// ...

b. Migrasi untuk posts:

BASH
php artisan make:migration create_posts_table

Edit file migrasi yang baru dibuat (database/migrations/..._create_posts_table.php):

PHP
// ...
public function up(): void
{
    Schema::create('posts', function (Blueprint $table) {
        $table->id();
        $table->foreignId('user_id')->constrained()->onDelete('cascade'); // Relasi ke tabel users
        $table->foreignId('category_id')->constrained()->onDelete('cascade'); // Relasi ke tabel categories
        $table->string('title');
        $table->string('slug')->unique();
        $table->text('content');
        $table->string('featured_image')->nullable();
        $table->enum('status', ['draft', 'published'])->default('draft');
        $table->timestamp('published_at')->nullable();
        $table->timestamps();
    });
}
// ...

Jalankan migrasi:

BASH
php artisan migrate

c. Model Category dan Post:

Buat model:

BASH
php artisan make:model Category
php artisan make:model Post

Edit app/Models/Category.php:

PHP
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Category extends Model
{
    use HasFactory;

    protected $fillable = ['name', 'slug'];

    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

Edit app/Models/Post.php:

PHP
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasFactory;

    protected $fillable = [
        'user_id',
        'category_id',
        'title',
        'slug',
        'content',
        'featured_image',
        'status',
        'published_at'
    ];

    protected $casts = [
        'published_at' => 'datetime',
    ];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function category()
    {
        return $this->belongsTo(Category::class);
    }

    // Otomatis generate slug saat menyimpan
    protected static function boot()
    {
        parent::boot();
        static::creating(function ($post) {
            $post->slug = \Illuminate\Support\Str::slug($post->title);
            if (static::where('slug', $post->slug)->exists()) {
                $post->slug = $post->slug . '-' . time();
            }
        });
        static::updating(function ($post) {
            if ($post->isDirty('title')) {
                $post->slug = \Illuminate\Support\Str::slug($post->title);
                if (static::where('slug', $post->slug)->where('id', '!=', $post->id)->exists()) {
                    $post->slug = $post->slug . '-' . time();
                }
            }
        });
    }
}

2. Controller untuk Admin (Kategori & Artikel)

Buat controller untuk mengelola kategori dan artikel di area admin.

BASH
php artisan make:controller Admin/CategoryController --resource
php artisan make:controller Admin/PostController --resource

a. app/Http/Controllers/Admin/CategoryController.php:

Implementasikan metode index, create, store, edit, update, destroy untuk CRUD kategori. Jangan lupa validasi dan flash message.

PHP
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Category;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

class CategoryController extends Controller
{
    public function index()
    {
        $categories = Category::latest()->paginate(10);
        return view('admin.categories.index', compact('categories'));
    }

    public function create()
    {
        return view('admin.categories.create');
    }

    public function store(Request $request)
    {
        $request->validate([
            'name' => 'required|string|max:255|unique:categories'
        ]);

        Category::create([
            'name' => $request->name,
            'slug' => Str::slug($request->name)
        ]);

        return redirect()->route('admin.categories.index')->with('success', 'Kategori berhasil ditambahkan!');
    }

    public function edit(Category $category)
    {
        return view('admin.categories.edit', compact('category'));
    }

    public function update(Request $request, Category $category)
    {
        $request->validate([
            'name' => 'required|string|max:255|unique:categories,name,' . $category->id
        ]);

        $category->update([
            'name' => $request->name,
            'slug' => Str::slug($request->name)
        ]);

        return redirect()->route('admin.categories.index')->with('success', 'Kategori berhasil diperbarui!');
    }

    public function destroy(Category $category)
    {
        $category->delete();
        return redirect()->route('admin.categories.index')->with('success', 'Kategori berhasil dihapus!');
    }
}

b. app/Http/Controllers/Admin/PostController.php:

Implementasikan metode index, create, store, show, edit, update, destroy untuk CRUD artikel. Termasuk upload gambar, validasi, dan flash message.

PHP
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Category;
use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

class PostController extends Controller
{
    public function index()
    {
        $posts = Post::with('user', 'category')->latest()->paginate(10);
        return view('admin.posts.index', compact('posts'));
    }

    public function create()
    {
        $categories = Category::all();
        return view('admin.posts.create', compact('categories'));
    }

    public function store(Request $request)
    {
        $request->validate([
            'title' => 'required|string|max:255',
            'category_id' => 'required|exists:categories,id',
            'content' => 'required',
            'featured_image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
            'status' => 'required|in:draft,published'
        ]);

        $imagePath = null;
        if ($request->hasFile('featured_image')) {
            $imagePath = $request->file('featured_image')->store('public/posts');
        }

        Post::create([
            'user_id' => auth()->id(),
            'category_id' => $request->category_id,
            'title' => $request->title,
            'slug' => Str::slug($request->title),
            'content' => $request->content,
            'featured_image' => $imagePath ? Storage::url($imagePath) : null,
            'status' => $request->status,
            'published_at' => $request->status === 'published' ? now() : null
        ]);

        return redirect()->route('admin.posts.index')->with('success', 'Artikel berhasil ditambahkan!');
    }

    public function show(Post $post)
    {
        return view('admin.posts.show', compact('post'));
    }

    public function edit(Post $post)
    {
        $categories = Category::all();
        return view('admin.posts.edit', compact('post', 'categories'));
    }

    public function update(Request $request, Post $post)
    {
        $request->validate([
            'title' => 'required|string|max:255',
            'category_id' => 'required|exists:categories,id',
            'content' => 'required',
            'featured_image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
            'status' => 'required|in:draft,published'
        ]);

        $imagePath = $post->featured_image;
        if ($request->hasFile('featured_image')) {
            // Hapus gambar lama jika ada
            if ($post->featured_image) {
                Storage::delete(str_replace('/storage', 'public', $post->featured_image));
            }
            $imagePath = $request->file('featured_image')->store('public/posts');
            $imagePath = Storage::url($imagePath);
        }

        $post->update([
            'category_id' => $request->category_id,
            'title' => $request->title,
            'slug' => Str::slug($request->title),
            'content' => $request->content,
            'featured_image' => $imagePath,
            'status' => $request->status,
            'published_at' => $request->status === 'published' && !$post->published_at ? now() : $post->published_at
        ]);

        return redirect()->route('admin.posts.index')->with('success', 'Artikel berhasil diperbarui!');
    }

    public function destroy(Post $post)
    {
        // Hapus gambar terkait jika ada
        if ($post->featured_image) {
            Storage::delete(str_replace('/storage', 'public', $post->featured_image));
        }
        $post->delete();
        return redirect()->route('admin.posts.index')->with('success', 'Artikel berhasil dihapus!');
    }
}

3. Controller untuk Halaman Publik

Buat controller untuk menampilkan daftar dan detail artikel yang sudah published.

BASH
php artisan make:controller BlogController

a. app/Http/Controllers/BlogController.php:

PHP
<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;

class BlogController extends Controller
{
    public function index()
    {
        $posts = Post::with('user', 'category')
                      ->where('status', 'published')
                      ->whereNotNull('published_at')
                      ->latest('published_at')
                      ->paginate(9);
        return view('blog.index', compact('posts'));
    }

    public function show(string $slug)
    {
        $post = Post::with('user', 'category')
                    ->where('slug', $slug)
                    ->where('status', 'published')
                    ->whereNotNull('published_at')
                    ->firstOrFail();
        return view('blog.show', compact('post'));
    }
}

4. Routing

Tambahkan route untuk area admin dan halaman publik di routes/web.php.

PHP
// ... (route login, register, dll)

Route::middleware(['auth'])->prefix('admin')->name('admin.')->group(function () {
    Route::get('/dashboard', function () {
        return view('admin.dashboard');
    })->name('dashboard');

    Route::resource('categories', Admin\CategoryController::class);
    Route::resource('posts', Admin\PostController::class);
});

// Public Blog Routes
Route::get('/blog', [App\Http\Controllers\BlogController::class, 'index'])->name('blog.index');
Route::get('/blog/{slug}', [App\Http\Controllers\BlogController::class, 'show'])->name('blog.show');

// ... (route default welcome)

5. Views (Blade)

Buat struktur folder resources/views/admin dan resources/views/blog.

a. Admin Layout:

Buat resources/views/layouts/admin.blade.php sebagai layout utama untuk area admin. Sertakan navigasi untuk Kategori dan Artikel.

b. Views Kategori:

  • resources/views/admin/categories/index.blade.php (daftar kategori)
  • resources/views/admin/categories/create.blade.php (form tambah kategori)
  • resources/views/admin/categories/edit.blade.php (form edit kategori)

c. Views Artikel:

  • resources/views/admin/posts/index.blade.php (daftar artikel)
  • resources/views/admin/posts/create.blade.php (form tambah artikel, dengan dropdown kategori dan input file)
  • resources/views/admin/posts/edit.blade.php (form edit artikel)
  • resources/views/admin/posts/show.blade.php (detail artikel)

d. Views Blog Publik:

  • resources/views/blog/index.blade.php (daftar artikel publik, dengan pagination)
  • resources/views/blog/show.blade.php (detail artikel publik)

Pastikan setiap form memiliki @csrf dan menampilkan error validasi (@error directive).

6. Konfigurasi Storage

Pastikan symbolic link untuk storage sudah dibuat agar gambar bisa diakses publik.

BASH
php artisan storage:link

Checklist Final Proyek Mini Blog

Setelah semua langkah di atas selesai, cek apakah fitur-fitur berikut sudah berfungsi dengan baik:

  • [ ] Sistem Login dan Register berfungsi.
  • [ ] Area admin (/admin) hanya bisa diakses setelah login.
  • [ ] CRUD Kategori berfungsi penuh (Tambah, Lihat, Edit, Hapus).
  • [ ] CRUD Artikel berfungsi penuh (Tambah, Lihat, Edit, Hapus).
  • [ ] Upload gambar untuk artikel berfungsi dan gambar ditampilkan dengan benar.
  • [ ] Status draft/published untuk artikel berfungsi.
  • [ ] Artikel yang draft tidak muncul di halaman publik.
  • [ ] Halaman daftar artikel publik (/blog) menampilkan artikel yang published.
  • [ ] Halaman detail artikel publik (/blog/{slug}) menampilkan artikel berdasarkan slug.
  • [ ] Artikel terhubung dengan user pembuat dan kategori.
  • [ ] Validasi input di semua form berfungsi.
  • [ ] Flash message (success/error) muncul setelah operasi.
  • [ ] Pagination berfungsi di daftar artikel admin dan publik.

Tantangan Lanjutan (Opsional)

Jika kamu ingin memperkaya proyek mini blog ini, berikut beberapa ide:

  • Fungsi Pencarian: Tambahkan fitur pencarian artikel.
  • Komentar Artikel: Izinkan pengguna (atau hanya yang login) untuk berkomentar.
  • Tagging Artikel: Tambahkan sistem tag untuk artikel.
  • Rich Text Editor: Integrasikan editor seperti TinyMCE atau CKEditor untuk content artikel.
  • Deployment: Coba deploy aplikasi ke hosting sungguhan (misalnya Heroku, DigitalOcean, Vercel).
  • Authorization: Buat sistem role (misalnya Admin, Editor) untuk membatasi akses.

Ringkasan Batch 10

Batch terakhir ini adalah bukti nyata bahwa kamu sudah menguasai dasar-dasar Laravel. Dengan membangun mini blog, kamu tidak hanya sekadar mengikuti tutorial, tapi benar-benar mengintegrasikan semua konsep menjadi satu aplikasi utuh. Ini adalah fondasi yang sangat kuat untuk membangun aplikasi Laravel yang lebih kompleks di masa depan.

Latihan Praktik

Tugas Utama: Bangunlah proyek mini blog ini dari awal (atau lanjutkan dari proyek sebelumnya) dengan mengikuti semua langkah yang telah dijelaskan. Pastikan semua fitur yang ada di Checklist Final Proyek Mini Blog berfungsi dengan baik.

Pertanyaan Cek Pemahaman

  1. Apa pentingnya mengintegrasikan semua komponen Laravel (routing, controller, model, view, auth, storage) dalam satu proyek?
  2. Bagaimana cara memastikan hanya artikel yang berstatus published yang muncul di halaman publik?
  3. Jelaskan alur kerja ketika sebuah gambar diunggah untuk featured image sebuah artikel, mulai dari form hingga penyimpanan di database dan server.
  4. Mengapa kita perlu membuat slug untuk artikel, dan bagaimana cara mengotomatisasinya di Laravel?
  5. Apa saja tantangan yang kamu hadapi saat mengintegrasikan berbagai fitur ini, dan bagaimana kamu menyelesaikannya?

Kesalahan Umum Pemula

  • Lupa php artisan storage:link: Gambar tidak akan muncul di browser.
  • Salah konfigurasi relasi di Model: user_id atau category_id tidak terisi, atau error saat eager loading.
  • Validasi tidak lengkap: Mengizinkan data kosong atau tidak valid masuk ke database.
  • Lupa @csrf di form: Error 419 Page Expired saat submit form POST.
  • Kesalahan di Blade: Variabel tidak terdefinisi atau sintaks Blade yang salah.
  • Middleware auth tidak diterapkan: Area admin bisa diakses tanpa login.
  • published_at tidak diatur: Artikel published tidak muncul karena published_at masih null.

Persiapan untuk Batch Berikutnya

Selamat, PahamITian! Kamu telah menyelesaikan seluruh kurikulum dasar Laravel ini. Ini adalah pencapaian besar! Tidak ada batch berikutnya dalam seri dasar ini, namun perjalanan belajarmu tidak berhenti di sini. Teruslah bereksplorasi dengan Laravel, coba tantangan lanjutan yang sudah diberikan, atau mulai proyek pribadi yang lebih ambisius. Dunia pengembangan web sangat luas, dan Laravel adalah alat yang sangat powerful untuk menjelajahinya.

Tetap semangat belajar dan jangan pernah berhenti berkreasi! Sampai jumpa di tutorial Pahamit.com lainnya!