Melanjutkan dari artikel sebelumnya Tutorial Laravel 12 Dari Nol: Pengenalan dan Instalasi - Eps 1, pada episode kali ini akan membahas tentang Migration dan CRUD di Laravel 12. Jika kamu belum membaca bagian pertama, sebaiknya baca terlebih dahulu agar lebih mudah mengikuti tutorial ini.
Membuat dan Menjalankan Migration
Migration di Laravel berfungsi untuk mengelola struktur database dengan cara yang terkontrol dan dapat dilacak. Alih-alih membuat tabel langsung melalui CLI (Command Line Interface) atau menggunakan GUI (Graphical User Interface) seperti phpMyAdmin. Hal ini memudahkan kolaborasi tim dan meminimalisir kesalahan, karena setiap perubahan struktur database dapat dilacak, didokumentasikan, dan dijalankan secara otomatis saat deploy ke berbagai lingkungan.
Pada tutorial Laravel 12 ini, kita akan membuat tabel untuk blog sederhana, yaitu:
1. categories: untuk menyimpan kategori artikel. Misalnya, kategori teknologi, lifestyle, edukasi, dan lain-lain.
2. posts: untuk menyimpan artikel/blog post. Misalnya, judul, konten, penulis (author), tanggal terbit, dan relasi ke kategori.
Membuat Migration
Untuk membuat migration, gunakan perintah artisan berikut di terminal atau command prompt pada direktori proyek Laravel:
php artisan make:migration create_categories_table --create=categorie
php artisan make:migration create_posts_table --create=posts
Perintah di atas akan membuat dua file migration baru, yaitu categories dan posts di folder database/migrations/. Setiap file migration berisi kode untuk mendefinisikan struktur tabel yang akan dibuat.
Hasil:

Pada file migration create_categories_table, isi schema create seperti berikut:
public function up(): void
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->timestamps();
});
}
Penjelasan:
- $table→id()
Membuat kolom id sebagai primary key bertipe big integer auto-increment.
- $table->string('name')->unique()
Membuat kolom nama kategori dengan tipe VARCHAR dengan nilai kolom unik.
- $table->timestamps()
Membuat dua kolom secara otomatis, yaitu created_at dan updated_at .
Pada file migration create_posts_table, isi schema create seperti berikut:
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->foreignId('category_id')->constrained('categories');
$table->timestamps();
$table->index('title');
});
}
Penjelasan:
- $table->id()
Membuat kolom id sebagai primary key bertipe big integer auto-increment.
- $table->string('title')
Membuat kolom untuk menyimpan judul post dengan tipe VARCHAR.
- $table->text('content')
Menyimpan isi post dengan tipe TEXT (lebih panjang dari string).
- $table->foreignId('category_id')->constrained('categories')
Membuat kolom foreign key category_id yang terhubung ke tabel categories.
- $table->timestamps()
Membuat dua kolom secara otomatis, yaitu created_at dan updated_at .
- $table->index('title')
Membuat index pada kolom title untuk mempercepat proses pencarian.
Menjalankan Migration
Setelah selesai membuat dan mengedit file migration, jalankan perintah berikut untuk membuat tabel di database sesuai struktur yang telah ditentukan:
php artisan migrate
Perintah ini akan menjalankan semua migration yang belum dijalankan, sehingga tabel categories dan posts akan otomatis tercipta di database tutorial_laravel_app yang telah di setup pada file .env .
Hasil:

Dengan migration, struktur database menjadi lebih terstandarisasi, mudah didokumentasikan, dan memudahkan pengelolaan database secara tim maupun individu di berbagai lingkungan pengembangan.
CRUD Sederhana dengan Laravel
Pada tahap ini, kita akan membangun fitur CRUD (Create, Read, Update, Delete) untuk mengelola data post dan menampilkannya berdasarkan kategori. Laravel memberikan struktur yang jelas dengan konsep MVC (Model, View, Controller) sehingga peletakan kode akan terorganisir dan mudah dikembangkan.
Tujuan utama:
- Membuat data post dengan memilih kategori.
- Menampilkan daftar semua post lengkap dengan kategorinya.
- Mengubah data post yang sudah ada.
- Menghapus post jika tidak dibutuhkan.
Laravel memudahkan semuanya melalui konsep Model, View, Controller seperti sebuah orkestra yang memainkan perannya masing-masing.
Implementasi Model, View, Controller (MVC)
Di Laravel, MVC bisa diibaratkan sebagai regu superhero:
- Model (Sang Ahli Database): Model adalah komponen yang bertugas “mengurus data dan database”. Di Laravel, model mewakili sebuah tabel di database dan berisi logika untuk mengelola, mengambil, menyimpan, atau memodifikasi data.
- View (Sang Seniman Tampilan): View adalah komponen yang bertugas menampilkan data ke pengguna. Di Laravel, view biasanya berupa file Blade (.blade.php) yang berisi HTML dan sintaks khusus Blade untuk menampilkan data secara dinamis.
- Controller (Sang Pengatur Strategi): Controller adalah komponen yang bertugas mengatur alur aplikasi dan logika bisnis. Di Laravel, controller menerima permintaan dari user (request), mengolah data dengan bantuan model, lalu mengirimkan hasil ke view.
1. Membuat Model dan Controller
Perintah berikut akan membuat model beserta controller untuk Post dan Category:
php artisan make:model Post -c
php artisan make:model Category -c
Penjelasan:
- Model akan dibuat di folder
app/Models/ - Controller akan dibuat di folder
app/Http/Controllers/
Isi file model Post.php:
<?php
namespace App\\Models;
use Illuminate\\Database\\Eloquent\\Model;
class Post extends Model
{
// kolom yang boleh diisi secara massal
protected $fillable = ['title', 'content', 'category_id'];
// relasi ke model Category
public function category()
{
return $this->belongsTo(Category::class);
}
}
Penjelasan:
- protected $fillable = [...]
Mengatur daftar kolom tabel yang boleh di-mass assign (diisi sekaligus melalui request). Ini mencegah serangan Mass Assignment Vulnerability.
- public function category()
Method untuk membuat relasi antar tabel dengan menggunakan relasi belongsTo , menandakan setiap post hanya memiliki satu kategori. Laravel otomatis menyambungkan foreign key category_id ke tabel categories .
Isi file model Category.php:
<?php
namespace App\\Models;
use Illuminate\\Database\\Eloquent\\Model;
class Category extends Model
{
// kolom yang boleh diisi secara massal
protected $fillable = ['name'];
// relasi ke model Post
public function posts()
{
return $this->hasMany(Post::class);
}
}
Penjelasan:
- protected $fillable = [...]
Mengatur daftar kolom tabel yang boleh di-mass assign (diisi sekaligus melalui request).
- public function post()
Method untuk membuat relasi antar post dengan category dengan menggunakan relasi hasMany. Artinya satu kategori dapat memiliki banyak post yang terkait.
2. Implementasi Controller
Setelah membuat model, langkah berikutnya adalah mengimplementasikan controller untuk menangani proses CRUD data post. Controller bertugas menerima request dari user, memproses data melalui model, dan mengirimkan hasilnya ke view.
isi file controller Post di folder app\\Http\\Controllers\\PostController.php:
<?php
namespace App\\Http\\Controllers;
use App\\Models\\Category;
use App\\Models\\Post;
use Illuminate\\Http\\Request;
class PostController extends Controller
{
/**
* Index method to display a list of posts.
*/
public function index()
{
$posts = Post::with('category')->OrderBy('id', 'desc')->paginate(10);
return view('posts.index', [
'title' => 'Daftar Post',
'posts' => $posts
]);
}
/**
* Create method to show the form for creating a new post.
*/
public function create()
{
$categories = Category::get();
return view('posts.create', [
'title' => 'Buat Post Baru',
'categories' => $categories
]);
}
/**
* Store method to save a new post.
*/
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required',
'content' => 'required',
'category_id' => 'required|exists:categories,id',
]);
Post::create($validated);
return redirect()->route('posts.index')->with('success', 'Post berhasil ditambahkan!');
}
/**
* Edit method to show the form for editing an existing post.
*/
public function edit(Post $post)
{
$categories = Category::all();
return view('posts.edit', [
'title' => 'Edit Post',
'post' => $post,
'categories' => $categories
]);
}
/**
* Update method to save changes to an existing post.
*/
public function update(Request $request, Post $post)
{
$validated = $request->validate([
'title' => 'required',
'content' => 'required',
'category_id' => 'required|exists:categories,id',
]);
$post->update($validated);
return redirect()->route('posts.index')->with('success', 'Post berhasil diperbarui!');
}
/**
* Destroy method to delete a post.
*/
public function destroy(Post $post)
{
$post->delete();
return redirect()->route('posts.index')->with('success', 'Post berhasil dihapus!');
}
}
Penjelasan:
- index()
Mengambil seluruh data post beserta kategori yang terkait, lalu mengirimkannya ke view untuk ditampilkan.
- create()
Mengambil semua kategori dari database untuk ditampilkan sebagai pilihan pada form tambah post.
- store()
Memvalidasi data yang dikirim dari form, lalu menyimpan post baru ke database.
- edit(Post $post)
Mengambil data post berdasarkan ID untuk ditampilkan di form edit, sekaligus mengambil daftar kategori.
- update(Request $request, Post $post)
Memvalidasi dan memperbarui data post yang sudah ada.
- destroy(Post $post)
Menghapus data post berdasarkan ID.
3. Implementasi View
View adalah komponen dalam arsitektur MVC (Model, View, Controller) yang bertanggung jawab untuk menampilkan data dan halaman kepada pengguna. Pada Laravel, view biasanya ditulis menggunakan Blade, yaitu sebuah template engine yang powerful dan ringan milik Laravel.
Fungsi Utama View:
- Menyajikan data dari controller (yang didapat dari model/database) dalam bentuk HTML atau tampilan web kepada user.
- Memisahkan logika tampilan dari logika bisnis, sehingga kode lebih rapi dan mudah dipelihara.
- Memudahkan pembuatan tampilan dinamis, seperti daftar data, form input, notifikasi, dll.
Langkah-Langkah Implementasi View
- Buat Folder posts di resources/views
Buka folder resources/views, lalu buat folder baru dengan nama posts.
- Buat File View di Dalam Folder posts
Di dalam folder resources/views/posts, buat file berikut:
- index.blade.php : Untuk menampilkan daftar post.
- create.blade.php : Untuk form tambah post.
- edit.blade.php : Untuk form edit post.
Isi file index.blade.php:
@extends('layouts.app')
@section('content')
<div class="flex flex-row justify-between items-center">
<h1 class="text-xl font-bold text-gray-900">Daftar Post</h1>
<a class="bg-blue-600 text-white px-4 py-2 rounded-lg" href="{{route('posts.create')}}">Create</a>
</div>
<div class="relative mt-6 overflow-x-auto shadow-md sm:rounded-lg">
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" class="px-6 py-3">
No
</th>
<th scope="col" class="px-6 py-3">
Judul
</th>
<th scope="col" class="px-6 py-3">
Aksi
</th>
</tr>
</thead>
<tbody>
@if($posts->count())
@foreach($posts as $post)
<tr class="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 border-b dark:border-gray-700 border-gray-200">
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
{{$loop->iteration}}
</th>
<td class="px-6 py-4">
{{$post->title}}
</td>
<td class="px-6 py-4 flex flex-row gap-6">
<a href="{{route('posts.edit', $post->id)}}" class="font-medium text-blue-600 dark:text-blue-500 hover:underline">Edit</a>
<form action="{{ route('posts.destroy', $post->id) }}" method="POST">
@csrf
@method('DELETE')
<button type="submit" class="cursor-pointer font-medium text-red-600 dark:text-red-500 hover:underline"
onclick="return confirm('Yakin ingin menghapus data ini?')">
Delete
</button>
</form>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="3" class="text-center py-4 text-gray-500">Belum ada data!</td>
</tr>
@endif
</tbody>
</table>
{{$posts->links('vendor.pagination.tailwind')}}
</div>
@endsection
Hasil jika posts belum ada data:

Hasil jika posts sudah ada data:

Isi file create.blade.php:
@extends('layouts.app')
@section('content')
<h1 class="text-xl font-bold text-gray-900">Create Post</h1>
<div class="relative mt-6 overflow-x-auto shadow-sm sm:rounded-lg">
<form class="w-full p-8 mx-auto" method="POST" action="{{route('posts.store')}}">
@csrf
<div class="mb-5">
<label for="title" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Judul</label>
<input name="title" type="text" id="title" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Judul Konten" required />
</div>
<div class="mb-5">
<label for="category" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Select an option</label>
<select name="category_id" id="category" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
<option value="">Pilih Kategori</option>
@foreach($categories as $category)
<option value="{{ $category->id }}">{{ $category->name }}</option>
@endforeach
</select>
</div>
<div class="mb-5">
<label for="content" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Konten</label>
<textarea name="content" id="content" rows="4" class="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Write your thoughts here..."></textarea>
</div>
<button type="submit" class="cursor-pointer text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Simpan</button>
</form>
</div>
@endsection
Hasil:

Isi file edit.blade.php:
@extends('layouts.app')
@section('content')
<h1 class="text-xl font-bold text-gray-900">Edit Post</h1>
<div class="relative mt-6 overflow-x-auto shadow-sm sm:rounded-lg">
<form class="w-full p-8 mx-auto" method="POST" action="{{ route('posts.update', $post->id) }}">
@csrf
@method('PUT')
<div class="mb-5">
<label for="title" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Judul</label>
<input name="title" type="text" id="title"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Judul Konten" value="{{ old('title', $post->title) }}" required />
</div>
<div class="mb-5">
<label for="category" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Select an option</label>
<select name="category_id" id="category"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
<option value="">Pilih Kategori</option>
@foreach($categories as $category)
<option value="{{ $category->id }}"
@if(old('category_id', $post->category_id) == $category->id) selected @endif>
{{ $category->name }}
</option>
@endforeach
</select>
</div>
<div class="mb-5">
<label for="content" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Konten</label>
<textarea name="content" id="content" rows="4"
class="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Write your thoughts here...">{{ old('content', $post->content) }}</textarea>
</div>
<button type="submit"
class="cursor-pointer text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
Ubah
</button>
</form>
</div>
@endsection
Hasil:

Routing
Routing adalah proses penghubung antara permintaan URL dari browser dengan aksi/controller di server. Pada Laravel, routing memungkinkan kita menentukan URL apa saja yang tersedia pada aplikasi, dan aksi apa yang dijalankan saat URL tersebut diakses. Semua konfigurasi routing pada aplikasi Laravel terdapat di file routes/web.php.
Pada aplikasi Laravel blog sederhana, kita ingin mengelola data seperti posts. Untuk memudahkan proses CRUD (Create, Read, Update, Delete), Laravel menyediakan resource route. Dengan satu baris kode, Laravel akan otomatis membuat semua route yang diperlukan untuk mengelola data posts.
Tambahkan route di file routes/web.php:
Route::resource('posts', 'App\\Http\\Controllers\\PostController');
Baris kode di atas akan menghasilkan 7 route secara otomatis, sesuai standar RESTful, yang terhubung dengan berbagai method di PostController:
- Method GET /post
Method Get akan menghasilkan URL /post. Ketika URL ini diakses, aplikasi akan menjalankan fungsi index pada PostController. Fungsinya adalah untuk menampilkan daftar seluruh post yang tersedia di database kepada pengguna.
- Method GET /post/create
URL ini memanggil fungsi create pada PostController. Fungsinya untuk menampilkan form tambah post baru kepada pengguna.
- Method POST /posts
Saat form tambah post dikirim, aplikasi menerima request POST ke URL ini. Fungsi store pada PostController dijalankan untuk menyimpan data post baru ke database.
- Method GET /posts/{post}
URL ini memanggil fungsi show pada PostController. Fungsinya untuk menampilkan detail satu post berdasarkan parameter {post} (biasanya dalam bentuk id).
- Method GET /posts/{post}/edit
URL ini memanggil fungsi edit pada PostController. Fungsinya untuk menampilkan form edit post berdasarkan parameter {post}.
- Method PATCH /posts/{post}
Saat form edit post dikirim, aplikasi menerima request PATCH ke URL ini. Fungsi update pada PostController dijalankan untuk menyimpan perubahan data post ke database.
- Method DELETE /posts/{post}
Saat tombol hapus post ditekan, aplikasi menerima request DELETE ke URL ini. Fungsi destroy pada PostController dijalankan untuk menghapus data post dari database.
Penutup
Demikianlah pembahasan mengenai Migration dan CRUD di Laravel 12. Dengan mengikuti langkah-langkah di atas, kita telah berhasil membuat struktur database yang rapi menggunakan migration, membangun fitur CRUD dengan konsep MVC (Model, View, Controller), serta memahami bagaimana routing bekerja untuk menghubungkan URL dengan controller dan view.
Materi ini adalah pondasi utama untuk aplikasi web berbasis Laravel. Setelah memahami konsep migration dan CRUD, kamu bisa mengembangkan fitur lebih lanjut seperti validasi data, relasi antar tabel yang lebih kompleks, hingga penerapan fitur-fitur modern lain di Laravel.
Untuk memperdalam pengetahuan dan skill tentang Laravel, kamu bisa mengikuti kelas-kelas di BuildWithAngga. Di sana, kamu akan belajar langsung dari praktisi berpengalaman, membangun project nyata, dan mendapatkan portfolio yang bisa meningkatkan kariermu. Mulai upgrade skill kamu sekarang dan raih peluang lebih luas bersama BuildWithAngga!