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.

dart

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 and updated_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.

dart

@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): VARCHAR
  • text(name): TEXT
  • boolean(name): BOOLEAN
  • integer(name): INT
  • bigInteger(name): BIGINT
  • float(name): FLOAT
  • timestamp(name): TIMESTAMP
  • date(name): DATE
  • json(name): JSON column
  • array(name): ARRAY column
  • foreignId(name): unsigned BIGINT for foreign key
  • enumColumn(name, values): ENUM column
  • timestamps(): adds created_at & updated_at
  • softDeletes(): adds deleted_at (soft delete)
  • morphs(name): polymorphic relation (type + id)
  • unique(): UNIQUE constraint
  • nullable(): allows NULL values
  • index(): adds index
  • defaultVal(value): default values
  • foreign(table, key): foreign key constraint
  • commentText(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.

dart

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.

dart

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.

dart

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.

On this page