Proyek Akhir: Blog Full-Stack Lengkap
Gabungkan semua: blog dengan auth, CRUD, komentar, dan upload.
Gambaran Proyek
Ini proyek akhir yang menggabungkan hampir semua modul Laravel: blog full-stack dengan halaman publik, dashboard penulis, komentar, upload cover, notifikasi, dan tags. Kerjakan bertahap; setiap fase bisa di-test sendiri sebelum lanjut.
Fase 1: Setup dan Auth
composer create-project laravel/laravel blogku && cd blogku
composer require laravel/breeze --dev
php artisan breeze:install blade && php artisan migrate
npm install && npm run dev # terminal terpisahBreeze memberi register/login/logout + layout Blade yang rapi. Jangan bangun auth manual, fokus ke fitur blog.
Fase 2: Database
php artisan make:model Post -m
php artisan make:model Comment -m
php artisan make:model Tag -m// migration posts
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('title');
$table->string('slug')->unique();
$table->text('body');
$table->string('cover')->nullable(); // path di disk public
$table->timestamp('published_at')->nullable();
$table->timestamps();
});
// migration comments
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->foreignId('post_id')->constrained()->cascadeOnDelete();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->text('body');
$table->timestamps();
});
// migration post_tag (pivot many-to-many)
Schema::create('post_tag', function (Blueprint $table) {
$table->foreignId('post_id')->constrained()->cascadeOnDelete();
$table->foreignId('tag_id')->constrained()->cascadeOnDelete();
$table->primary(['post_id', 'tag_id']);
});Relasi di model plus route binding via slug:
// app/Models/Post.php
public function user() { return $this->belongsTo(User::class); }
public function comments() { return $this->hasMany(Comment::class)->latest(); }
public function tags() { return $this->belongsToMany(Tag::class); }
public function getRouteKeyName(): string { return 'slug'; }Buat factory + seeder agar punya data dummy: php artisan make:factory PostFactory, lalu php artisan db:seed.
Fase 3: Halaman Publik (Tanpa N+1)
// app/Http/Controllers/PostController.php
public function index()
{
// with() = eager loading, paginate = 12 per halaman
$posts = Post::with(['user', 'tags'])
->whereNotNull('published_at')
->latest('published_at')
->paginate(12);
return view('posts.index', compact('posts'));
}
public function show(Post $post)
{
$post->load(['user', 'tags', 'comments.user']);
return view('posts.show', compact('post'));
}Satu with() menghindari N+1: tanpa ini, 12 post = 1 query post + 12 query user + 12 query tags. Cek dengan Laravel Debugbar sampai query per halaman masuk akal.
Fase 4: Dashboard CRUD + Upload Cover
php artisan make:controller Dashboard/PostController
php artisan make:request StorePostRequest
php artisan make:policy PostPolicy --model=Post// app/Http/Controllers/Dashboard/PostController.php
public function store(StorePostRequest $request)
{
$data = $request->validated();
$data['slug'] = Str::slug($data['title']).'-'.Str::random(6);
if ($request->hasFile('cover')) {
$data['cover'] = $request->file('cover')->store('covers', 'public');
}
$request->user()->posts()->create($data);
return redirect()->route('dashboard.posts.index');
}Validasi di StorePostRequest: title required, body min 50, cover nullable image max 2MB, tags array dengan tags.* exists. Setelah update, sinkronkan: $post->tags()->sync($request->tags). Saat ganti cover, hapus yang lama via Storage::disk('public')->delete($post->cover).
Fase 5: Komentar + Notifikasi
// app/Http/Controllers/CommentController.php
public function store(Request $request, Post $post)
{
$data = $request->validate(['body' => 'required|string|max:1000']);
$comment = $post->comments()->create([
...$data, 'user_id' => $request->user()->id,
]);
// beri tahu pemilik post (jangan notifikasi diri sendiri)
if ($post->user_id !== $request->user()->id) {
$post->user->notify(new KomentarBaru($comment));
}
return back();
}Buat notification KomentarBaru (channel mail + database, ShouldQueue) seperti di modul notification, plus link "Notifikasi" di navbar yang menampilkan $user->unreadNotifications.
Fase 6: Policy dan Keamanan
// app/Policies/PostPolicy.php
public function update(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
public function delete(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}Proteksi route dashboard: Route::middleware(['auth', 'verified'])->prefix('dashboard')->group(...). Tulis feature test: user B PUT ke post user A harus 403.
Fase 7: Checklist Selesai dan Deploy
- Semua halaman jalan tanpa error, validasi rapi dengan pesan error
- Tidak ada N+1 (verifikasi dengan Debugbar)
- Policy ter-test: user lain tidak bisa edit/hapus
-
php artisan testhijau
Untuk deploy ke shared hosting/VPS: APP_ENV=production, APP_DEBUG=false, php artisan optimize, document root ke folder public, dan storage/ writable. Cantumkan URL demo di CV, employer lebih percaya demo hidup daripada screenshot.
Catatan teknis: Slug unik penting untuk SEO dan URL cantik. Tambahan
Str::random(6)mencegah tabrakan saat dua post berjudul sama. Dan jangan lupa: kolomslugharus punya unique index di migration, kalau tidak validasi keunikan tidak terjamin di level database.
Tantangan
Wireframe Dulu
Gambar wireframe 5 halaman blog (beranda, detail, dashboard, form post, login) di kertas/Figma. Tentukan juga tabel database-nya. Baru mulai coding setelah desain jelas.
Tugas
Tugas: Blog Full-Stack
Bangun blog full-stack sesuai spesifikasi. Kirimkan ringkasan: daftar fitur yang selesai, URL demo (jika di-deploy), dan 3 file yang paling kamu banggakan (paste kodenya). AI tutor menilai arsitektur, keamanan, dan kelengkapan.
Kriteria penilaian:
- Semua fitur spesifikasi berfungsi
- Policy dan validasi benar, tidak ada celah obvious
- Tidak ada N+1 query di halaman daftar
- Kode rapi mengikuti konvensi Laravel
AI tutor akan memeriksa bug, kesalahan syntax, dan memberi saran perbaikan.
Kuis Bab
Uji pemahamanmu: Bab 10: Arsitektur dan Proyek Akhir
Jawab 5 soal berikut, lalu tekan "Periksa Jawaban".
1.Apa beda register() dan boot() di Service Provider?
2.Apa keuntungan bind interface di container?
3.Kapan memakai Event/Listener?
4.Untuk API autentikasi SPA/mobile, pilih apa?
5.Langkah pertama sebelum coding proyek akhir menurut materi?