Belajar Laravel #24: Proyek Kecil CRUD — Merangkai Migration sampai Blade

Di episode ke-24 ini saya menyatukan materi dari episode 2 sampai 12 dalam satu proyek nyata: aplikasi manajemen tugas sederhana. Sengaja saya pilih proyek kecil seperti ini karena selama ini saya hanya menguji tiap fitur Laravel secara terpisah — migration jalan sendiri, controller jalan sendiri, Blade jalan sendiri — dan saya belum pernah merangkainya jadi satu alur utuh dari nol sampai jadi.

Diagram alur request Laravel: route resource ke controller, model, database, Blade view, dan empat operasi CRUD
Alur satu request Laravel dan empat operasi CRUD yang dipakai di proyek ini.

Hasilnya jujur saja: ada beberapa bagian yang ternyata saling bergantung lebih erat dari yang saya kira. Saya catat alur pengerjaannya di sini supaya nanti kalau saya mulai proyek berikutnya, saya tidak perlu mengingat-ingat lagi urutan yang benar.

1. Rancang dulu, jangan langsung coding

Pelajaran pertama: saya tergoda langsung mengetik php artisan make:model tanpa tahu field apa saja yang saya butuhkan. Akhirnya migration saya edit berkali-kali. Untuk aplikasi tugas, saya tetapkan dulu di kertas:

  • tasks — id, title, description, status (pending/done), due_date, user_id, timestamps
  • users — sudah ada dari migration bawaan, cukup untuk login

Ini berarti bagi saya: lima menit menyusun daftar kolom di kertas jauh lebih murah daripada menjalankan migration tiga kali.

2. Migration dan model

Saya buat migration dan model sekaligus dalam satu perintah, lalu isi definisi kolomnya:

php artisan make:model Task -m

Di file migration saya tambahkan kolomnya. Yang saya catat di sini: enum ternyata merepotkan kalau nanti mau menambah status baru, jadi saya pakai string biasa dengan default ‘pending’ supaya fleksibel.

Schema::create('tasks', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('description')->nullable();
    $table->string('status')->default('pending');
    $table->date('due_date')->nullable();
    $table->foreignId('user_id')->constrained()->cascadeOnDelete();
    $table->timestamps();
});

Kemudian php artisan migrate. Kalau ada kesalahan, php artisan migrate:rollback mengembalikan database ke keadaan semula — fitur inilah yang membuat saya berani bereksperimen.

3. Routing, controller, dan resource

Untuk CRUD lengkap, saya tidak menulis route satu per satu. Cukup satu baris resource route:

Route::resource('tasks', TaskController::class)->middleware('auth');

Perintah itu membuat tujuh route sekaligus (index, create, store, show, edit, update, destroy) yang sudah sesuai konvensi nama metode controller. Di dalam controller saya isi logikanya: index mengambil daftar tugas milik user yang login, store memvalidasi lalu menyimpan, destroy menghapus. Saya bungkus semua dengan middleware auth supaya tamu tidak bisa mengakses — ini materi episode 9 yang langsung terasa gunanya.

4. Validasi sebelum menyimpan

Bagian yang paling sering saya lupakan waktu belajar dulu: validasi. Saya pilih cara paling ringkas untuk proyek kecil, yaitu validate() langsung di controller. Kalau nanti proyeknya membesar, saya pindahkan ke Form Request seperti di episode 8.

$data = $request->validate([
    'title'       => 'required|max:255',
    'description' => 'nullable|string',
    'due_date'    => 'nullable|date',
]);

Error validasi otomatis dilempar balik ke halaman sebelumnya beserta input lama, dan tinggal saya tampilkan di Blade dengan @error. CSRF token juga wajib ada di setiap form — kalau lupa, Laravel menolak request POST-nya.

5. Blade: layout satu induk, banyak anak

Saya buat satu layout induk layouts/app.blade.php berisi navbar dan container, lalu tiap halaman hanya mengisi bagian kosongnya. Ini pola @extends dan @section dari episode 5 yang membuat tampilan konsisten tanpa menyalin navbar di tiap file.

@extends('layouts.app')

@section('content')
<h1>Daftar Tugas</h1>
@forelse ($tasks as $task)
    <div class="task">
        <strong>{{ $task->title }}</strong>
        <span>{{ $task->status }}</span>
    </div>
@empty
    <p>Belum ada tugas.</p>
@endforelse
@endsection

@forelse saya sukai karena sekaligus menangani kondisi kosong — tidak perlu menulis @if(count(...)) terpisah.

6. Menyambungkan tugas ke user yang login

Kesalahan saya waktu pertama kali: menyimpan task tanpa user_id, sehingga semua tugas jadi milik semua orang. Perbaikannya satu baris, mengambil user dari guard yang sedang login:

$request->user()->tasks()->create($data);

Baris ini hanya jalan kalau di model User sudah ada relasi hasMany(Task::class) — materi episode 7 yang ternyata jadi penghubung wajib.

7. Menjalankan dan menguji manual

Setelah semua terpasang, saya jalankan dengan php artisan serve dan menguji satu per satu di browser: login, membuat tugas, memperbarui statusnya jadi done, lalu menghapus. Yang paling membantu adalah membuka halaman tugas dalam dua tab berbeda dengan dua akun berbeda — tes sederhana ini yang akhirnya menangkap bahwa tugas milik akun A masih bisa dilihat akun B lewat URL /tasks/1 milik orang lain. Solusinya: di method show dan update saya selalu menambahkan ->where('user_id', auth()->id()) sebelum mengambil record.

Saya juga menyalakan APP_DEBUG=true hanya di lokal. Di server produksi variabel ini harus tetap false, karena halaman error Laravel menampilkan potongan kode dan isi environment yang tidak boleh dilihat orang asing.

Yang saya pelajari dari rangkaian ini

Saya menutup proyek kecil ini dengan tiga catatan. Pertama, urutan kerja yang paling efisien menurut pengalaman saya: migration → model → route → controller → view. Kalau saya mulai dari view, saya selalu kehilangan arah. Kedua, resource route dan Eloquent menghapus begitu banyak kode berulang — saya cukup fokus pada logika bisnisnya saja. Ketiga, validasi dan CSRF bukan opsional; keduanya adalah tameng termurah yang bisa dipasang.

Ada satu hal yang belum saya kerjakan: pencarian dan pagination. Untuk daftar tugas yang masih puluhan baris belum terasa, tapi saya catat supaya di proyek berikutnya saya tambahkan ->paginate(10) sejak awal. Berikutnya di episode 25, saya menutup seri ini dengan roadmap belajar lanjutan — Livewire, Filament, dan sertifikasi. Tapi untuk sekarang, saya cukup puas karena akhirnya punya satu aplikasi kecil yang berjalan utuh di lokal.

Sumber

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *

Situs ini menggunakan Akismet untuk mengurangi spam. Pelajari bagaimana data komentar Anda diproses