Migrations
Migrations in Khadem are the foundation of managing your database schema in a structured, safe, and consistent way. They enable schema versioning, rollback, and reproducibility across different environments.
What Are Migrations?
Migrations are Dart classes that describe the transformation of your database schema using a programmatic builder. Each migration file contains an up()
method (to apply changes) and a down()
method (to revert them).
They act as version-controlled scripts and are executed sequentially by the Migrator engine.
Creating Your First Migration
To create a migration, extend the MigrationFile
contract and define the schema inside builder.create
.
import 'package:khadem/khadem_dart.dart';
class CreateUsersTable extends MigrationFile {
@override
Future<void> up(builder) async {
builder.create('users', (table) {
table.id();
table.string('name');
table.string('email').unique();
table.string('password');
table.timestamps();
});
}
@override
Future<void> down(builder) async {
builder.dropIfExists('users');
}
}
- table.id(): Adds an auto-incrementing primary key.
- table.timestamps(): Adds
created_at
andupdated_at
. - table.string(): Creates a VARCHAR field (default: 255 chars).
- unique(): Adds a UNIQUE constraint to the column.
Reversing Migrations
The down()
method is used to revert the changes applied by the up()
method. You typically call dropIfExists
to delete the table.
@override
Future<void> down(builder) async {
builder.dropIfExists('users');
}
You can also drop columns or foreign keys inside down()
when performing more complex operations.
Schema Builder Methods
Inside the builder callback, you get access to the Blueprint
object which provides dozens of chainable methods to define your table structure.
id()
: Primary Key (auto-increment)string(name, length)
: VARCHARtext(name)
: TEXTboolean(name)
: BOOLEANinteger(name)
: INTbigInteger(name)
: BIGINTfloat(name)
: FLOATtimestamp(name)
: TIMESTAMPdate(name)
: DATEjson(name)
: JSON columnarray(name)
: ARRAY column
foreignId(name)
: unsigned BIGINT for foreign keyenumColumn(name, values)
: ENUM columntimestamps()
: adds created_at & updated_atsoftDeletes()
: adds deleted_at (soft delete)morphs(name)
: polymorphic relation (type + id)unique()
: UNIQUE constraintnullable()
: allows NULL valuesindex()
: adds indexdefaultVal(value)
: default valuesforeign(table, key)
: foreign key constraintcommentText(text)
: column comment
Running Migrations
The Migrator
engine provides several methods to control migrations programmatically. It uses a dedicated migrations
table to track executed scripts.
final migrator = Migrator(databaseManager);
migrator.registerAll([
CreateUsersTable(),
CreatePostsTable(),
]);
await migrator.upAll(); // Run all pending migrations
await migrator.downAll(); // Rollback all migrations
await migrator.refresh(); // Reset (down + up)
await migrator.reset(); // Same as refresh
await migrator.status(); // Show migration status
// Individual migration control
await migrator.up('CreateUsersTable');
await migrator.down('CreateUsersTable');
upAll() runs all pending migrations.
downAll() rolls back everything in reverse order.
refresh() = downAll + upAll (complete reset).
reset() = downAll + upAll (same as refresh).
status() prints all migration states.
up(name) runs a specific migration by name.
down(name) rolls back a specific migration by name.
Individual Migration Control
You can run or rollback specific migrations by name, which is useful for testing or partial deployments.
final migrator = Migrator(databaseManager);
// Check migration status first
await migrator.status();
// Run specific migrations
await migrator.up('CreateUsersTable');
await migrator.up('CreatePostsTable');
// Rollback specific migrations
await migrator.down('CreatePostsTable');
await migrator.down('CreateUsersTable');
Use migrator.status()
to see which migrations have been run and which are pending.
Foreign Keys and Relationships
Khadem migrations support foreign key constraints to maintain referential integrity between tables.
class CreatePostsTable extends MigrationFile {
@override
Future<void> up(builder) async {
builder.create('posts', (table) {
table.id();
table.string('title');
table.text('content');
// Foreign key with CASCADE delete
table.foreignId('user_id')
.foreign('users', 'id')
.onDelete('CASCADE')
.onUpdate('CASCADE');
table.timestamps();
});
}
@override
Future<void> down(builder) async {
builder.dropIfExists('posts');
}
}
- foreignId(): Creates an unsigned BIGINT column for foreign keys.
- foreign(): Adds foreign key constraint with CASCADE/RESTRICT options.
- onDelete(): Defines action when parent record is deleted.
- onUpdate(): Defines action when parent record is updated.
Next Up
Explore how to create Models and interact with your tables using Dart classes.