Belajar Laravel #5: Migration & Schema Database — Versi Control untuk Struktur Tabel

Di episode keempat kita sudah mulai bikin controller yang isinya logika CRUD — mulai dari index(), store() sampai destroy(). Tapi ada satu hal yang saya belum bahas di sana: dari mana data itu datang? Controller-nya sudah jadi, view-nya sudah ada, tapi tabelnya belum ada sama sekali. Hari ini saya belajar soal itu: migration — cara Laravel memanage struktur database lewat kode PHP, bukan lewat klik di phpMyAdmin.

Kenapa database harus dikode juga?

Saya punya kebiasaan lama: bikin tabel lewat GUI — buka phpMyAdmin, klik “New Table”, isi kolom satu per satu, save. Memang cepat untuk prototipe, tapi begitu ada tim atau ada perubahan di bulan kedua, semua jadi kacau. Saya tidak tahu tabel “tasks” sudah diubah kapan, oleh siapa, dan apa perubahannya. Di sinilah migrasi masuk.

Bayangkan ini seperti git tapi untuk database. Setiap kali ada perubahan struktur — tambah kolom, buat tabel baru, ubah relasi — kita tulis perubahan itu sebagai “migration file”. File ini disimpan di folder database/migrations/ dan bisa di-rollback kalau ada kesalahan. Dengan begini, seluruh tim punya riwayat perubahan database yang jelas dan bisa diputar ulang.

Membuat migration pertama

Perintah untuk membuat migration sangat sederhana lewat Artisan:

php artisan make:migration create_tasks_table

Perintah ini menghasilkan satu file PHP di database/migrations/ dengan nama yang sudah ditandai timestamp — misalnya 2026_08_26_000000_create_tasks_table.php. Timestamp ini penting karena Laravel menggunakan urutan waktu untuk menentukan urutan eksekusi migrasi.

Isi file yang dihasilkan kurang lebih seperti ini:

<?php

use Illuminate\\Database\\Migrations\\Migration;
use Illuminate\\Database\\Schema\\Blueprint;
use Illuminate\\Support\\Facades\\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('tasks', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('tasks');
    }
};

Ada dua method utama di sini. Method up() berisi perintah untuk membuat atau mengubah struktur — ini yang dijalankan saat kita menjalankan migrasi. Method down() berisi kebalikannya, untuk membatalkan perubahan. Laravel memanggil down() saat kita menjalankan migrate:rollback. Ini prinsip yang sederhana tapi sangat powerful: setiap perubahan harus bisa dibatalkan.

Schema facade dan Blueprint

Yang saya pelajari di sini: Laravel tidak langsung menulis SQL. Ia menggunakan Schema::create() dengan closure yang menerima objek Blueprint. Blueprint inilah yang memberikan kita method-method seperti string(), text(), boolean(), dan seterusnya. Setiap method ini akan diterjemahkan ke SQL yang sesuai untuk database yang kita pakai — MySQL, PostgreSQL, SQLite, atau lainnya. Analoginya seperti Google Translate untuk struktur database: kita menulis dalam bahasa PHP yang mudah dipahami, Laravel yang menerjemahkan ke SQL yang sesuai.

Tipe kolom yang sering dipakai

Saya rangkum tipe kolom yang paling sering saya temui di proyek Laravel berdasarkan kebutuhan umum:

Tipe BlueprintSQL TypeCocok Untuk
$table->id()BIGINT UNSIGNED AUTO_INCREMENTPrimary key — selalu ada di setiap tabel
$table->string('name')VARCHAR(255)Nama, email, judul, slug — teks pendek
$table->text('body')TEXTIsi konten, deskripsi panjang, catatan
$table->integer('age')INTAngka: usia, jumlah, hitungan
$table->boolean('active')TINYINT(1)True/false: aktif/tidak, published/draft
$table->decimal('price', 8, 2)DECIMAL(8,2)Harga, rating, nilai presisi
$table->timestamp('published_at')DATETIMETanggal spesifik: waktu publish, deadline
$table->foreignId('user_id')->constrained()BIGINT UNSIGNED + FOREIGN KEYRelasi ke tabel lain — case ini ke users

Beberapa catatan yang saya pelajari dari pengalaman: string() default-nya VARCHAR(255) — kalau butuh lebih panjang, bisa pakai string('name', 500). Untuk boolean(), Laravel menyimpannya sebagai 0 atau 1 di database, tapi di PHP kita bisa langsung pakai true/false. Dan yang paling sering bikin saya lupa: timestamps() otomatis membuat dua kolom created_at dan updated_at — Laravel mengisinya secara otomatis lewat Eloquent, jadi kita tidak perlu mengisi manual.

Contoh migration yang lengkap

Mari kita isi migration file tasks dengan kolom yang relevan. Dari pengalaman saya belajar di episode sebelumnya, tabel tasks butuh kolom: judul, isi tugas, status selesai/belum, dan relasi ke user yang punya tugas itu:

public function up(): void
{
    Schema::create('tasks', function (Blueprint $table) {
        $table->id();
        $table->foreignId('user_id')->constrained()->cascadeOnDelete();
        $table->string('title');
        $table->text('description')->nullable();
        $table->boolean('is_completed')->default(false);
        $table->timestamps();
    });
}

Perhatikan beberapa hal di sini. foreignId('user_id')->constrained() membuat kolom user_id dengan tipe BIGINT UNSIGNED, lalu menambahkan constraint foreign key yang merujuk ke tabel users. cascadeOnDelete() berarti kalau user dihapus, semua tasks milik ikut terhapus — ini yang paling umum untuk relasi parent-child. nullable() pada description berarti kolom itu boleh kosong. Dan default(false) pada is_completed memberikan nilai awal false saat data baru dibuat.

Menjalankan migration

Setelah migration file siap, jalankan perintah berikut untuk membuat tabel di database:

# Jalankan semua migration yang belum dijalankan
php artisan migrate

# Lihat status migration
php artisan migrate:status

Saat pertama kali menjalankan php artisan migrate di proyek baru, Laravel otomatis membuat tabel migrations di database — ini adalah “log” yang mencatat migration mana saja yang sudah dijalankan. Ketika kita menjalankan migrate lagi di masa depan, Laravel hanya menjalankan migration yang belum ada di log ini. Sistem ini membuat migrasi idempoten — aman dijalankan berulang kali tanpa efek samping.

Rollback: membatalkan perubahan

Ini bagian yang paling saya syukuri dari migration. Kalau ternyata ada kesalahan — misalnya kolom yang salah tipe atau relasi yang belum benar — cukup jalankan:

# Batalkan migration terakhir (satu batch)
php artisan migrate:rollback

# Batalkan semua migration
php artisan migrate:fresh

# Hapus data, lalu jalankan ulang semua migration
php artisan migrate:fresh --seed

migrate:rollback hanya membatalkan migration terakhir (satu batch). Kalau mau reset semuanya dari nol — cocok untuk development — pakai migrate:fresh. Tapi kalau pakai --seed, jangan lupa untuk menyiapkan seeders juga karena data yang sudah ada akan hilang. Di produksi, tentu saja kita harus sangat hati-hati dengan perintah ini.

Menambah kolom di migration yang sudah ada

Kadang kita tidak selalu membuat tabel baru — terkadang ada kolom yang perlu ditambahkan ke tabel yang sudah ada. Laravel punya command untuk ini:

php artisan make:migration add_priority_to_tasks_table --table=tasks

Perintah ini membuat migration dengan Schema::table() alih-alih Schema::create(). Kita cukup menambahkan kolom baru di method up():

public function up(): void
{
    Schema::table('tasks', function (Blueprint $table) {
        $table->unsignedTinyInteger('priority')->default(0);
    });
}

public function down(): void
{
    Schema::table('tasks', function (Blueprint $table) {
        $table->dropColumn('priority');
    });
}

Ini seperti menambahkan kolom baru ke tabel yang sudah ada tanpa mengganggu data yang sudah ada. Dan karena ada down(), kita bisa menghapus kolom ini juga kalau rollback dilakukan. Metode ini juga bisa dipakai untuk menambahkan foreign key, index, atau unique constraint ke tabel yang sudah ada.

Kesimpulan

Migrasi bagi saya seperti-versi control database yang selama ini saya tidak tahu saya butuhkan. Sebelum belajar Laravel, saya membuat tabel langsung lewat GUI atau SQL manual, dan hasilnya: tidak ada catatan riwayat, tidak ada cara untuk membatalkan perubahan secara bersih, dan kerja tim jadi mengerikan. Dengan migration, saya punya satu sumber kebenaran untuk struktur database, bisa di-rollback kalau salah, dan bisa dijalankan ulang oleh siapa saja yang clone proyek ini. Ini prinsip yang sangat sederhana tapi dampaknya luar biasa pada produktivitas.

Saya juga sadar bahwa belajar migration ini adalah fondasi penting untuk episode-episode berikutnya — terutama saat belajar Eloquent ORM dan relasi antar tabel, yang mana sangat bergantung pada struktur database yang benar dan terdokumentasi lewat migration.


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