Migration adalah file PHP yang menulis satu perubahan struktur database: bikin tabel, tambah kolom, hapus kolom. Laravel menjalankan file-file itu berurutan dan mencatat mana yang sudah jalan, jadi database di laptopmu, di laptop temanmu, dan di server punya tabel yang sama persis. Bayangin buku resep renovasi yang langkahnya bernomor, plus daftar centang di dinding. Tukangnya cuma mengerjakan langkah yang belum dicentang, lalu mencentangnya. Langkah yang sudah dicentang gak dikerjakan lagi, walau tulisan di resepnya kamu ubah. Di Laravel, tukangnya adalah Artisan, dan daftar centangnya adalah tabel bernama migrations.
🧪 Tebakan cepat: kamu sudah menjalankan
php artisan migrateuntuk tabelbooks. Lalu kamu menghapus->nullable()dari kolomisbndi file yang sama, dan menjalankanmigratelagi. Sekarangisbnwajib diisi? Tebak dulu, jawabannya ada di bagian "Laravel cuma mencocokkan nama file".
Membuat file migration
Sampai pelajaran Blade, BookController menyimpan buku di array PHP, jadi buku baru hilang begitu request selesai. Sekarang buku pindah ke database, ke tabel books. Buat file-nya dengan Artisan:
php artisan make:migration create_books_table
INFO Migration [/home/budi/perpustakaan/database/migrations/2026_10_09_090000_create_books_table.php] created successfully.
- Nama file diawali tanggal dan jam (
2026_10_09_090000). Laravel menjalankan migration urut dari yang paling tua. - Dari nama
create_books_table, Laravel menebak kamu mau membuat tabelbooks, jadi file-nya langsung berisiSchema::create('books', ...)dengan$table->id()dan$table->timestamps(). Kolom lainnya kamu tambahkan sendiri.
Isi file: up() dan down()
<?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('books', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('author');
$table->integer('stock')->default(0);
$table->string('isbn')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('books');
}
};
up()berisi perubahannya.down()berisi kebalikannya, dipakai saat perubahan itu dibatalkan (rollback).Schema::create('books', ...)membuat tabelbooks.$tableadalah Blueprint, "denah" tabelnya: tiap baris$table->...menambah satu kolom.$table->id()membuat kolomid, angka yang naik sendiri dan jadi primary key (dibahas di pelajaran Primary Key & Foreign Key).$table->string('title')membuat kolom teks pendek, maksimal 255 karakter. Panjang lain ditulisstring('title', 100).$table->integer('stock')->default(0)membuat kolom bilangan bulat untuk jumlah eksemplar.->default(0)mengisinya 0 kalau gak diisi.$table->string('isbn')->nullable()membuat kolom yang boleh kosong (NULL), karena buku lama sering gak punya ISBN. Tanpa->nullable()atau->default(...), setiap kolom wajib diisi.- Untuk benar atau salah ada
$table->boolean(...), misalnya$table->boolean('borrowable')->default(true): boleh dipinjam, bawaannya true. $table->timestamps()(pakai s) membuat dua kolom sekaligus,created_atdanupdated_at, yang nanti diisi Eloquent otomatis.Schema::dropIfExists('books')menghapus tabelnya, kalau ada.
Laravel menerjemahkan Blueprint ke SQL sesuai database-nya. Ini hasil persis Laravel 13:
| Blueprint | MySQL | SQLite |
|---|---|---|
$table->id() |
bigint unsigned not null auto_increment primary key |
integer primary key autoincrement not null |
$table->string('title') |
varchar(255) not null |
varchar not null |
->integer('stock')->default(0) |
int not null default '0' |
integer not null default '0' |
->string('isbn')->nullable() |
varchar(255) null |
varchar |
->boolean('borrowable')->default(true) |
tinyint(1) not null default '1' |
tinyint(1) not null default '1' |
$table->timestamps() |
created_at dan updated_at, timestamp null |
created_at dan updated_at, datetime |
Menjalankan: migrate, status, rollback
$ php artisan migrate
INFO Preparing database.
Creating migration table ....................................... 3.51ms DONE
INFO Running migrations.
2026_10_09_090000_create_books_table ........................... 1.99ms DONE
$ php artisan migrate:status
Migration name .............................................. Batch / Status
2026_10_09_090000_create_books_table ............................... [1] Ran
$ php artisan migrate:rollback
INFO Rolling back migrations.
2026_10_09_090000_create_books_table ........................... 0.85ms DONE
php artisan migratemenjalankanup()setiap file yang belum tercatat. Yang pertama kali, dia membuat tabelmigrationsdulu ("Creating migration table").- Setelah
up()berhasil, nama file-nya dicatat di tabelmigrationsbeserta nomor batch. Satu kalimigrate= satu batch, berapa pun file yang jalan. php artisan migrate:statusmembandingkan folderdatabase/migrationsdengan tabel itu:[1] Ranartinya sudah jalan di batch 1,Pendingartinya belum.php artisan migrate:rollbackmenjalankandown()untuk file-file di batch terakhir, lalu menghapus catatannya. Statusnya kembaliPending.php artisan migrate:freshmen-drop semua tabel lalu menjalankan semua migration dari awal. Cuma untuk data coba-coba di laptop.
Laravel cuma mencocokkan nama file
Sebelum menjalankan apa pun, migrate membaca nama-nama di tabel migrations. File yang namanya sudah ada di sana dilewati, apa pun isinya sekarang. Jadi menghapus ->nullable() lalu migrate lagi cuma menjawab:
INFO Nothing to migrate.
Kolom isbn di database tetap boleh kosong. Itu jawaban tebakannya. Ada dua cara yang benar:
- File-nya belum dipakai siapa pun (baru di laptopmu, belum di-push):
migrate:rollback, edit file-nya, lalumigratelagi. - Sudah dijalankan temanmu atau server: jangan sentuh file lama. Buat migration baru, misalnya
php artisan make:migration add_year_to_books_table:
// up()
Schema::table('books', function (Blueprint $table) {
$table->integer('year')->nullable();
});
// down()
Schema::table('books', function (Blueprint $table) {
$table->dropColumn('year');
});
Schema::table mengubah tabel yang sudah ada, bukan membuat baru. Kolom barunya nullable(), karena buku yang sudah tersimpan belum punya tahun terbit. Di SQLite jadi alter table "books" add column "year" integer. Cara mengubah kolom di production tanpa bikin error dibahas di pelajaran Schema Migration Patterns.
nullable() dan default(): wajib atau boleh kosong
Coba simpan satu buku cuma dengan judul dan penulis (kode Eloquent ini dibahas di pelajaran berikutnya):
Book::create(['title' => 'Laskar Pelangi', 'author' => 'Andrea Hirata']);
Kolom isbn di migration |
Hasilnya |
|---|---|
->nullable() |
masuk: stock 0 dari default(0), isbn NULL |
tanpa ->nullable() |
ditolak: SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL constraint failed: books.isbn |
Database yang menolaknya, bukan Laravel. Tiap kolom yang boleh gak diisi butuh ->nullable() atau ->default(...).
⚠️ Jebakan yang sering ketemu
- Mengedit migration yang sudah jalan. Hasilnya
Nothing to migrate.dan database-mu gak berubah. Rollback dulu (kalau cuma di laptopmu), atau buat migration baru. down()gak membalikup(). Kalauup()membuat tabel,down()harusSchema::dropIfExists(...)tabel yang sama. Kalau gak, rollback gagal atau meninggalkan sisa.migrate:freshdi server. Semua tabel di-drop, semua data hilang.timestampvstimestamps.$table->timestamp('created_at')cuma satu kolom.$table->timestamps()membuat dua-duanya. Latihannya memeriksa$table->timestamps()di kodemu.
TL;DR: Migration adalah file PHP berisi up() (perubahan) dan down() (kebalikannya), dibuat dengan php artisan make:migration create_books_table. Di dalam Schema::create, $table->id(), string() (teks maksimal 255), integer(), boolean(), dan timestamps() (dua kolom: created_at dan updated_at) menambah kolom, dan setiap kolom wajib diisi kecuali diberi ->nullable() atau ->default(...). php artisan migrate menjalankan file yang belum tercatat di tabel migrations dan memberinya nomor batch, migrate:status menampilkan Ran atau Pending, migrate:rollback membatalkan batch terakhir. File yang sudah tercatat gak pernah dijalankan lagi, jadi perubahan berikutnya ditulis di migration baru (Schema::table).