Migrations

Database Migrations is a process of creating, altering and dropping database tables from code, instead of writing SQL queries. AdonisJs has out of the box support for database migrations so that you can set up your database tables within the application codebase.

Introduction

Naive Way

The naive approach of database management is to create the SQL database tables manually. Which work, but it is not great as it requires a lot of manual re-work.

Let’s take an example of a standard procedure of creating database tables.

  1. Login to SequelPro(or similar) and create database tables.

  2. Now when someone wants to work on the same application, you need to share the database schema or dump with them.

  3. When they make some changes in the database, they have to re-share the new database dump with you.

  4. You cannot run automated tests on TravisCI(or similar) as you need access to the server to create the database tables.

  5. Finally, when deploying the app in production, you need to make a remote connection to your server database to create those tables manually.

Migrations

With migrations SQL schema is part of your code base which means once your co-workers will check out the code from a git client, they will get the database schema with it, and any progressive changes made by them are available to you.

About Migrations

  1. Migrations are stored inside database/migrations folder.

  2. Each schema file is an ES2015 class with two required methods called up and down.

  3. The up method get executed when running the migrations.

  4. The down method get called during migrations rollback.

  5. Migrations are executed in a batch and give you the flexibility to rollback to a given batch.

Basic Example

Now as you know about the benefits of migrations, let’s take a closer look on how to create SQL database tables using Javascript.

./ace make:migration users --create=users
Output
create: database/migrations/1464437815620_users.js
database/migrations/1464437815620_users.js
'use strict'

const Schema = use('Schema')

class UsersSchema extends Schema {
  up () {
    this.create('users', (table) => {
      table.increments()
      table.timestamps()
    })
  }

  down () {
    this.drop('users')
  }
}

module.exports = UsersSchema

Running Migrations

The migration:run command is used to execute all the migrations stored inside database/migrations directory. AdonisJs will smartly skip the migrations which have been executed already and no executes the new ones.

./ace migration:run

It is recommended to work with incremental migrations, instead of modifying the same schema file every time. For example

  1. Create a new schema file when you want to create a new table.

  2. Down the line, you realized that you need two extra fields in the same table. Create a new schema file to alter the existing table and add those two new fields.

  3. Again after few days, you have a requirement to rename an existing field. Create a new schema file for that also.

Incremental migrations help you in tracking changes like Version control. When writing code, it is a good practice to make small code commits, so that you have cleaner code history. You must treat your migrations the same way.

List Of Commands

Below is the list of ace commands specific to migrations only.

Command Options Purpose

migration:run

none

Run all pending migrations by executing up method of all schema files.

migration:rollback

[batch=last]

Rollback migrations to a given batch or default to last batch.

migration:refresh

none

Refresh migrations by dropping and re-running all migrations

migration:reset

none

Rollback to the initial state.

migration:status

none

Check the current status of migrations.

Interacting With Tables

create(tableName, callback)

Create a new database table.

class UserSchema {
  up () {
    this.create('users', (table) => {
      ...
    })
  }
}

createIfNotExists(tableName, callback)

Only creates the table if it does not exists, otherwise silently ignores the create command.

class UserSchema {
  up () {
    this.createIfNotExists('users', (table) => {
      ...
    })
  }
}

rename(from, to)

Rename an existing database table.

class UserSchema {
  up () {
    this.rename('users', 'my_users')
  }
}

drop(tableName)

Drop an existing database table.

class UserSchema {
  down () {
    this.drop('users')
  }
}

dropIfExists(tableName)

Drop database table only if it exists, otherwise silently ignores the drop command.

class UserSchema {
  down () {
    this.dropIfExists('users')
  }
}

has(tableName)

Resolves with a boolean indicating whether a database table exists or not.

class UserSchema {
  up () {
    this.has('users').then((exists) => {
      if (!exists) {
        // do something
      }
    })
  }
}

table(tableName, callback)

Select a table for alter.

class UserSchema {
  up () {
    this.table('users', (table) => {
      table.dropColumn('deleted_at')
    })
  }
}

raw(statement)

Run an arbitrary SQL query in the schema builder chain.

class UserSchema {
  up () {
    this.raw('SET sql_mode="TRADITIONAL"')
    .create('users', (table) => {
      table.increments()
    })
  }
}

db(closure)

The db method will give you the access to the Database query builder within your migrations. It is helpful if you want to migrate data while altering the database tables.

class UserSchema {
  up () {
    this.db(function * (database) {
      const names = yield database.from('users').pluck('name')
    })
  }
}

Schema Builder

Please refer to the docs of Knex Schema Building, everything from knex is fully supported. Below is the example making use of schema builder to create the users table.

'use strict'

const Schema = use('Schema')

class UsersSchema extends Schema {
  up () {
    this.create('users', (table) => {
      table.increments()
      table.string('username').unique()
      table.string('email').unique()
      table.string('password', 60)
      table.timestamps()
      table.softDeletes()
    })
  }

  down () {
    this.drop('users')
  }
}

module.exports = UsersSchema