Skip to content

Database Migrations ​

Migrations are like version control for your database, allowing your team to define and share the application's database schema definition. If you have ever had to tell a teammate to manually add a column to their local database schema, you've faced the problem that database migrations solve.

Bavard's migration system provides a simple way to build and modify tables using a fluent, database-agnostic interface.

Introduction ​

Migrations are stored in the root directory database/migrations in your project. Each migration file contains a class that extends Migration and defines two methods: up and down.

  • The up method is used to add new tables, columns, or indexes to your database.
  • The down method should reverse the operations performed by the up method.

Generating Migrations ​

To create a new migration, use the make:migration CLI command:

bash
dart run bavard make:migration create_users_table

This will create a new file in your database/migrations directory. The file name includes a timestamp to ensure migrations run in the correct order.

INFO

You can change the base path where migrations are located by specifying --path=

Migration Structure ​

A migration class looks like this:

dart
import 'package:bavard_migration/bavard_migration.dart';

class CreateUsersTable extends Migration {
  @override
  Future<void> up(Schema schema) async {
    await schema.create('users', (Blueprint table) {
      table.id();
      table.string('name');
      table.string('email').unique();
      table.timestamps();
    });
  }

  @override
  Future<void> down(Schema schema) async {
    await schema.dropIfExists('users');
  }
}

Running Migrations ​

To run all of your outstanding migrations, execute the migrate command:

bash
dart run bavard migrate

This command will run all migrations that haven't been executed yet, in date order.

Rolling Back Migrations ​

To reverse the last batch of migrations that were run, use the migrate:rollback command:

bash
dart run bavard migrate:rollback

This will call the down method of the migrations in the last batch.

Tables ​

Creating Tables ​

To create a new table, use the create method on the Schema object. The create method accepts two arguments: the name of the table and a callback that receives a Blueprint object, which you can use to define the new table:

dart
await schema.create('users', (Blueprint table) {
  table.id();
  table.string('username');
});

Modifying Tables ​

To update an existing table, use the table method. This allows you to add or drop columns and indexes:

dart
await schema.table('users', (Blueprint table) {
  table.string('phone').nullable(); // Add a new column
  table.dropColumn('age');          // Drop an existing column
  table.renameColumn('bio', 'description'); // Rename a column
});

Note: When using SQLite, some operations (like dropping foreign keys or modifying columns) might be limited or require a table rebuild. Bavard handles basic operations seamlessly, but complex modifications on SQLite should be tested carefully.

Dropping Tables ​

To drop an existing table, you may use the drop or dropIfExists methods:

dart
await schema.drop('users');

await schema.dropIfExists('users');

Blueprint Reference ​

The Blueprint object passed to your migration callbacks offers a wide range of methods to define your table structure.

Column Types ​

MethodDescription
bigIncrements('id')Auto-incrementing UNSIGNED BIGINT (Primary Key).
increments('id')Auto-incrementing UNSIGNED INTEGER (Primary Key).
id()Alias for bigIncrements('id').
string('name', [length])VARCHAR column with optional length (default 255).
text('description')TEXT column.
mediumText('body')MEDIUMTEXT column.
longText('history')LONGTEXT column.
char('code', [length])CHAR column with fixed length.
integer('votes')INTEGER column.
tinyInteger('status')TINYINT column.
smallInteger('rank')SMALLINT column.
mediumInteger('code')MEDIUMINT column.
bigInteger('views')BIGINT column.
unsignedInteger('age')UNSIGNED INTEGER column.
float('amount')FLOAT column.
double('precision')DOUBLE column.
decimal('price', [p, s])DECIMAL column with precision and scale.
boolean('confirmed')BOOLEAN column.
date('dob')DATE column.
dateTime('created_at')DATETIME column.
dateTimeTz('created_at')DATETIME with Timezone.
time('start')TIME column.
timeTz('start')TIME with Timezone.
timestamp('added_on')TIMESTAMP column.
timestampTz('added_on')TIMESTAMP with Timezone.
timestamps()Adds nullable created_at and updated_at.
timestampsTz()Adds nullable timezone-aware created_at and updated_at.
softDeletes()Adds nullable deleted_at timestamp.
softDeletesTz()Adds nullable timezone-aware deleted_at.
binary('data')BLOB / Binary column.
json('options')JSON column.
jsonb('meta')JSONB column (Binary JSON).
uuid('uid')UUID column.
ipAddress('visitor')IP Address column.
macAddress('device')MAC Address column.
enumCol('role', ['admin', 'user'])ENUM column with allowed values.
rememberToken()Adds nullable remember_token VARCHAR(100).
morphs('taggable')Adds taggable_id (unsigned big int) and taggable_type.
uuidMorphs('taggable')Adds taggable_id (UUID) and taggable_type.

Column Modifiers ​

You can chain these methods onto any column definition:

ModifierDescription
.nullable()Allows NULL values to be inserted into the column.
.defaultTo(value)Declare a "default" value for the column.
.unsigned()Set INTEGER columns to UNSIGNED.
.useCurrentTimestamp()Set TIMESTAMP columns to use CURRENT_TIMESTAMP as default.
.unique()Add a unique index to the column.
.primary()Add a primary key index to the column.
.change()Mark the column to be modified (for ALTER TABLE operations).

Indexing ​

MethodDescription
primary('id')Add a primary key.
primary(['first', 'last'])Add a composite primary key.
unique('email')Add a unique index.
index('state')Add a basic index.
fullText('body')Add a FullText index.
spatialIndex('location')Add a Spatial index.
foreign('user_id')Start defining a foreign key constraint.

Foreign Key Constraints ​

Use the foreign method to define constraints:

dart
table.foreign('user_id')
     .references('id')
     .on('users')
     .onDelete('cascade');

Available methods on the foreign key definition:

  • references('column'): The column on the parent table.
  • on('table'): The parent table name.
  • onDelete('action'): Action for delete (e.g., cascade, set null).
  • onUpdate('action'): Action for update.

Released under the MIT License. Crafted with ❤️ for the Dart ecosystem.