Skip to main 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 after pulling in your changes from source control, you've faced the problem that database migrations solve.

Out of the box, Formidable provides a simple database migration system that allows you to define and share your database schema definition. This is a great way to ensure that your database schema is always in sync with your application code.

Formidable Database Migrations are stored in the /database/migrations directory and are powered by Knex.js.

Creating a Migrationโ€‹

To create a new migration, run the following command:

node craftsman make:migration CreatePostsTable --table=posts

The command above will create a new migration file under /database/migrations and will generate the following migration:

const { Database } = require('@formidablejs/framework');

/** @param {Database} DB */
exports.up = (DB) => {
return DB.schema.createTable('posts', (table) => {

});
};

/** @param {Database} DB */
exports.down = (DB) => DB.schema.dropTable('posts');

We can now define our migration's schema in the up function:

exports.up = (DB) => {
return DB.schema.createTable('posts', (table) => {
table.increments('id').primary();
table.foreign('user_id').references('id').inTable('users').onDelete('cascade');
table.string('title');
table.text('body');
table.timestamps();
});
};

After defining our schema, we can now run the migration:

node craftsman migrate:latest

This will add a new table to your database.

Migration Modificationsโ€‹

Formidable allows you to modify existing tables by creating new migrations that modify the existing tables.

Adding A Columnโ€‹

Here is an example of a migration that adds a new column to an existing table:

node craftsman make:migration AddSoftDeletesToPostsTable --table=posts --alter
const { Database } = require('@formidablejs/framework');

/**
* Add a softDeletes (delete_at) column to the posts table.
*
* @param {Database} DB
*/
exports.up = (DB) => {
return DB.schema.table('posts', (table) => {
table.timestamp('deleted_at').nullable();
});
};

/**
* Remove the softDeletes (deleted_at) column from the posts table.
*
* @param {Database} DB
*/
exports.down = (DB) => {
return DB.schema.table('posts', (table) => {
table.schema.dropColumn('deleted_at')
});
};

This migration will add a new column to the posts table when migrate up is ran, and will remove the column when migrate down is ran.

Removing A Columnโ€‹

Here is an example of a migration that removes a column from an existing table:

const { Database } = require('@formidablejs/framework');

/**
* Remove the softDeletes (deleted_at) column from the posts table.
*
* @param {Database} DB
*/
exports.up = (DB) => {
return DB.schema.table('posts', (table) => {
table.dropColumn('deleted_at');
});
};

/**
* Add a softDeletes (delete_at) column to the posts table.
*
* @param {Database} DB
*/
exports.down = (DB) => {
return DB.schema.table('posts', (table) => {
table.timestamp('deleted_at').nullable();
});
};

Renaming A Columnโ€‹

Here is an example of a migration that renames a column in an existing table:

const { Database } = require('@formidablejs/framework');

/**
* Rename the deleted_at column to delete_when in the posts table.
*
* @param {Database} DB
*/
exports.up = (DB) => DB.schema.table('posts').renameColumn('deleted_at', 'deleted_when');

/**
* Rename the deleted_when column from posts table back to deleted_at.
*
* @param {Database} DB
*/
exports.down = (DB) => DB.schema.table('posts').renameColumn('deleted_when', 'deleted_at');

Changing A Columnโ€‹

Here is an example of a migration that changes a column in an existing table:

const { Database } = require('@formidablejs/framework');

/**
* Change the type of the deleted_at column from timestamp to boolean.
*
* @param {Database} DB
*/
exports.up = (DB) => {
return DB.schema.table('posts', (table) => {
table.boolean('deleted_at').alter();
});
};

/**
* Change the type of the deleted_at column from boolean to timestamp and make it nullable.
*
* @param {Database} DB
*/
exports.down = (DB) => {
return DB.schema.table('posts', (table) => {
table.timestamp('deleted_at').nullable().alter();
});
};

Running Migrationsโ€‹

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

node craftsman migrate:latest

To run the next outstanding migration, execute the migrate:up Craftsman command:

node craftsman migrate:up

To run a specific migration, execute the migrate:up Craftsman command with the -m option:

node craftsman migrate:up -m 20210820161410_create_posts_table

Rolling Back Migrationsโ€‹

To roll back all of your migrations, execute the migrate:rollback Craftsman command:

node craftsman migrate:rollback

To roll back a specific migration, execute the migrate:down Craftsman command with the -m option:

node craftsman migrate:down -m 20210820161410_create_posts_table

Schema Builderโ€‹

The Formidable Framework uses Knex.js to build database schemas.

Let's take a look at an example migration:

const { Database } = require('@formidablejs/framework');

/**
* Create the posts table.
*
* @param {Database} DB
*/
exports.up = (DB) => {
return DB.schema.createTable('posts', (table) => {
table.increments('id').primary();
table.foreign('user_id').references('id').inTable('users').onDelete('cascade');
table.string('title');
table.text('body');
table.timestamps();
});
};

/**
* Drop the posts table.
*
* @param {Database} DB
*/
exports.down = (DB) => DB.schema.dropTable('posts');

The up method receives a Database instance as its first argument. This instance provides a variety of methods that may be used to define the schema for the table. The down method receives the same Database instance, allowing you to reverse the operations performed by the up method.

Creating Tablesโ€‹

To create a new database table, use the createTable method on the Database instance you receive in your migration:

exports.up = (DB) => {
return DB.schema.createTable('users', (table) => {
table.increments('id').primary();
table.string('email').unique();
table.string('password');
table.timestamps();
});
};

The createTable method accepts two arguments: the first is the name of the table, while the second is a callback which receives a TableBuilder instance that may be used to define the new table's columns.

Column Typesโ€‹

The TableBuilder class contains a variety of column types that you may use when building your tables:

exports.up = (DB) => {
return DB.schema.createTable('users', (table) => {
table.increments('id').primary();
table.string('email').unique();
table.string('password');
table.timestamps();
});
};

Schema Buildingโ€‹

dropColumnโ€‹

Drops a column, specified by the column's name

table.dropColumn('deleted_at');
dropColumnsโ€‹

Drops multiple columns, specified by an array of column names

table.dropColumns(['deleted_at', 'deleted_by']);
renameColumnโ€‹

Renames a column from one name to another

table.renameColumn('deleted_at', 'deleted_when');
rememberTokenโ€‹

Adds a remember_token column to the table

table.rememberToken();
incrementsโ€‹

Adds an auto incrementing column. This is the same as integer with autoIncrement set to true.

table.increments('id').primary();
integerโ€‹

Adds an integer column.

table.integer('age');
bigIntegerโ€‹

Adds a big integer column.

table.bigInteger('views');
tinyintโ€‹

Adds a tiny integer column.

table.tinyint('views');
smallintโ€‹

Adds a small integer column.

table.smallint('views');
mediumintโ€‹

Adds a medium integer column.

table.mediumint('views');
bigintโ€‹

Adds a big integer column.

table.bigint('views');
textโ€‹

Adds a text column.

table.text('description');
stringโ€‹

Adds a string column.

table.string('email');
floatโ€‹

Adds a float column.

table.float('amount');
doubleโ€‹

Adds a double column.

table.double('amount');
decimalโ€‹

Adds a decimal column.

table.decimal('amount');
booleanโ€‹

Adds a boolean column.

table.boolean('confirmed');
dateโ€‹

Adds a date column.

table.date('created_at');
datetimeโ€‹

Adds a datetime column.

table.datetime('created_at');
timeโ€‹

Adds a time column.

table.time('sunrise');
timestampโ€‹

Adds a timestamp column.

table.timestamp('created_at');
timestampsโ€‹

Adds created_at and updated_at columns.

table.timestamps();
dropTimestampsโ€‹

Drops created_at and updated_at columns.

table.dropTimestamps();
softDeletesโ€‹

Adds a deleted_at column to the table.

table.softDeletes();
dropSoftDeletesโ€‹

Drops the deleted_at column from the table.

table.dropSoftDeletes();
binaryโ€‹

Adds a binary column.

table.binary('photo');
enumโ€‹

Adds an enum column.

table.enum('role', ['admin', 'user']);
jsonโ€‹

Adds a json column.

table.json('options');
jsonbโ€‹

Adds a jsonb column.

table.jsonb('options');
uuidโ€‹

Adds a uuid column.

table.uuid('id').defaultTo(DB.fn.uuid());
commentโ€‹

Adds a comment to the column.

table.string('email').comment('The user\'s email address');
engineโ€‹

Sets the table's storage engine.

table.engine('InnoDB');
charsetโ€‹

Sets the table's character set.

table.charset('utf8mb4');
collateโ€‹

Sets the table's collation.

table.collate('utf8mb4_unicode_ci');
inheritsโ€‹

Sets the table's inheritance.

table.inherits('users');
specificTypeโ€‹

Sets the table's specific type.

table.specificType('email', 'varchar(100)');
indexโ€‹

Adds an index.

table.index('email');
dropIndexโ€‹

Drops an index.

table.dropIndex('email');
setNullableโ€‹

Sets the column to be nullable.

table.string('email').setNullable();
dropNullableโ€‹

Drops the column's nullable property.

table.string('email').dropNullable();
primaryโ€‹

Sets the column to be the primary key.

table.string('email').primary();
uniqueโ€‹

Sets the column to be unique.

table.string('email').unique();
foreignโ€‹

Sets the column to be a foreign key.

table.foreign('user_id').references('id').inTable('users').onDelete('cascade');
dropForeignโ€‹

Drops the column's foreign key.

table.dropForeign('user_id');
dropUniqueโ€‹

Drops the column's unique property.

table.string('email').dropUnique();
dropPrimaryโ€‹

Drops the column's primary property.

table.string('email').dropPrimary();

For more information, visit the Knex.js documentations.