Node.js
PostgreSQL
DB

Node.jsのSequelizeでDBのmigrationを実行する

More than 1 year has passed since last update.

Node.jsのORMである Sequelize はその基となったActiveRecordと同様にDBのマイグレーション機構も備わっています。 Sequelize のマイグレーション操作についてはまとまった記事が見当たらなかったので後世のために設定から基本操作までここに記しておこうと思います。
ここでは接続先のDBとしてPostgreSQLを利用していますが、MySQL、SQLiteでも同じ操作でmigrationが実行できます。

  • 実行環境・ライブラリのバージョン情報
node: 7.5.0
sequelize: 3.30.2
sequelize-cli: 2.5.1

追記(2017年2月19日)

DB接続の準備

まず、動作確認用のプロジェクトを作成します。

mkdir migration-test
cd migration-test
npm init

sequelize 本体と PostgreSQL を扱うためのライブラリをインストールします。

npm install sequelize --save
npm install pg --save

コマンドライン実行のセットアップ

コマンドラインから sequelize のmigrationを実行できるように sequelize-cli をインストールします。

npm install sequelize-cli --save

以下のようにプロジェクトにインストールされた sequelize シェルを実行します。

node_modules/.bin/sequelize -v

グローバルインストールした場合は以下のように sequelize だけで実行できます。

npm install sequelize-cli -g
sequelize -v

DB接続設定

以下のコマンドで config ディレクトリとその直下に config.json が生成されます。
sequelizeでのmigration実行では config.json の設定を使用してDBに接続します。

node_modules/.bin/sequelize init

config.json は生成直後は以下の状態です。
host username password をファイルに直接記述するのは避けたいところです。
ちなみに dialect は利用するDBの種類になります。

{
  "development": {
    "username": "root",
    "password": null,
    "database": "database_development",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "test": {
    "username": "root",
    "password": null,
    "database": "database_test",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "production": {
    "username": "root",
    "password": null,
    "database": "database_production",
    "host": "127.0.0.1",
    "dialect": "mysql"
  }
}

DB接続設定は環境依存情報なので環境変数から取得するのがセオリーです。
config.json を以下のように書き換えます。
簡略化するために test 環境の設定を省きましたが必要ならば追加してください。 
唐突に出現した use_env_variable が気になる人は こちら のコードを追ってみてください。
DATABASE_URL のところは任意の文字列でよいのですが、Heroku環境にデプロイする際に都合がよい ので DATABASE_URL にしています。

{
  "development": {
    "use_env_variable": "DATABASE_URL"
  },
  "production": {
    "use_env_variable": "DATABASE_URL"
  }
}

環境変数に DATABASE_URL を追加します。
PostgreSQLの場合の DATABASE_URL の書式は以下のようになります。

[DATABASE_URL]
postgres://[user_name]:[password]@[host]:[port]/[database_name]
設定
host localhost
user_name user01
password qazwsx
port 5432
database_name dev

接続設定が上記の値ならば DATABASE_URL は以下のようになります。

[DATABASE_URL]
postgres://user01:qazwsx@localhost:5432/dev

export コマンドで環境変数として登録します。

export DATABASE_URL=postgres://user01:qazwsx@localhost:5432/dev

新規テーブルの作成

雛型ファイルの生成

テーブル生成のための雛型は sequelize model:create というコマンドで生成できます。
以下のようにオプションとして --name --attributes を指定すると素敵です。
カラム名をスネークケースにする場合は自動付加されるタイムスタンプ列のために --underscored を指定します。 --underscored を指定しない場合は createdAt updatedAt というカラム名になります。

node_modules/.bin/sequelize model:create --name user --underscored --attributes name:string,birth:date,country_code:integer

migrations フォルダ下に (実行日時)-create-user.js というファイル名のJavaScriptファイルが生成されます。 Ruby on Rails に慣れている人なら見覚えのある形式だと気づくかもしれません。
必要に応じて allowNulldefault 値を設定します。

'use strict';
module.exports = {
  up: function(queryInterface, Sequelize) {
    return queryInterface.createTable('users', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      name: {
        type: Sequelize.STRING
      },
      birth: {
        type: Sequelize.DATE
      },
      country_code: {
        type: Sequelize.INTEGER
      },
      created_at: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updated_at: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: function(queryInterface, Sequelize) {
    return queryInterface.dropTable('users');
  }
};

models フォルダ下には user.js が生成されます。こちらはアプリケーション実行時に利用されるものです。

'use strict';
module.exports = function(sequelize, DataTypes) {
  var User = sequelize.define('User', {
    name: DataTypes.STRING,
    birth: DataTypes.DATE,
    country_code: DataTypes.INTEGER
  }, {
    classMethods: {
      associate: function(models) {
        // associations can be defined here
      }
    }
  });
  return User;
};

migrationの実行

db:migrate コマンドで生成したスクリプトをDBに適用します。
--env オプションを指定するとその環境のみに適用されます。
指定しない場合は config.json に記述されたすべての環境に適用されるので普通は --env を指定しますね。

node_modules/.bin/sequelize db:migrate --env development

pgAdminで確認してみると users テーブルと SequelizeMeta テーブルが作成されているのが分かります。 SequelizeMeta テーブルにはmigrationファイルのファイル名のレコードが作成されます。

pgAdmin.png

ロールバックする場合は以下のコマンドを実行します。
ロールバックで指定した処理が実行され、 SequelizeMeta の該当のレコードが削除されます。

node_modules/.bin/sequelize db:migrate:undo --env development

既存テーブルへのカラム追加

雛型ファイルの生成

以下のコマンドで既存テーブルのマイグレーションスクリプトの雛型を生成できます。
migrations フォルダ下に (実行日時)-user.js というファイル名のJavaScriptファイルが作成されます。 --name オプションを指定しないと (実行日時)-unnamed-migration.js というファイル名です。
migration:generatemigration:create の結果はどちらも同じなので使いやすい方を利用しましょう。個人的には migration:generate の方がしっくりきます。

node_modules/.bin/sequelize migration:generate --name user
node_modules/.bin/sequelize migration:create --name user
'use strict';

module.exports = {
  up: function (queryInterface, Sequelize) {
    // コメント部分は省略しています
  },

  down: function (queryInterface, Sequelize) {
    // コメント部分は省略しています
  }
};

以前に作成した users テーブルに emailprefecture_code カラムを追加します。
queryInterface.addColumn メソッドにテーブル名、カラム名とカラムの詳細設定を渡します。
残念ながらPostgreSQLは任意のカラムの後にカラムを追加することはできず、最後尾に追加することしかできません。そのため before after オプションがあるのですが機能しません。MySQLを利用する場合に検討してください。
down にはロールバック用のカラム削除処理を記述します。

'use strict';

module.exports = {
  up: function (queryInterface, Sequelize) {
    return [
      queryInterface.addColumn('users', 'email', {
        type: Sequelize.STRING,
        after: 'country_code' // PostgreSQLでは無効なオプションです
      }),
      queryInterface.addColumn('users', 'prefecture_code', {
        type: Sequelize.INTEGER,
        allowNull: false,
        defaultValue: 0,
        after: 'email' // PostgreSQLでは無効なオプションです
      })
    ];
  },

  down: function (queryInterface, Sequelize) {
    return [
      queryInterface.removeColumn('users', 'email'),
      queryInterface.removeColumn('users', 'prefecture_code')
    ];
  }
};

migrationの実行

テーブル作成時と同じく、 sequelize db:migrate で適用します。

node_modules/.bin/sequelize db:migrate --env development

ロールバック処理も同様です。

node_modules/.bin/sequelize db:migrate:undo --env development

カラム追加のマイグレーション実行後の users テーブルの構造です。

image.png

インデックスの追加

既存のテーブルにインデックスを追加する方法です。カラム追加の時と同じようにまずは雛型のファイルを作成します。

node_modules/.bin/sequelize migration:generate --name user

既存の name 列のインデックスとユニーク制約を追加、 新規に last_login_at 列を追加してインデックスにします。
インデックスの追加は addIndex メソッドを使用します。テーブル名と対象となる列の組み合わせ、インデックスの詳細を設定します。

'use strict';

module.exports = {
  up: function (queryInterface, Sequelize) {
    return [
      queryInterface.addIndex(
        'users',
        ['name'],
        {
          indexName: 'users_name_index',
          indicesType: 'UNIQUE' // ユニークインデックスになります
        }
      ),
      queryInterface.addColumn('users', 'last_login_at', {
        type: Sequelize.DATE
      }),
      queryInterface.addIndex(
        'users',
        ['last_login_at'],
        {
          indexName: 'users_last_login_at_index' // こちらは単なるインデックス
        }
      ),
    ];
  },

  down: function (queryInterface, Sequelize) {
    return [
      queryInterface.removeIndex('users', 'users_name_index'),
      queryInterface.removeIndex('users', 'users_last_login_at_index')
    ];
  }
};

sequelize db:migrate でマイグレーションを実行すると2つのインデックスが作成されます。

image.png

複合主キーの記述方法

複数の列で一意になる複合主キーが必要な場合は主キーを構成する列全てに primaryKey: true を記述します。以下のテーブルは user_idfriend_id の2つで複合主キーとなります。

'use strict';
module.exports = {
  up: function(queryInterface, Sequelize) {
    return queryInterface.createTable('friends', {
      user_id: {
        type: Sequelize.INTEGER,
        allowNull: false,
        primaryKey: true
      },
      friend_id: {
        type: Sequelize.INTEGER,
        allowNull: false,
        primaryKey: true
      },
      created_at: {
        type: Sequelize.DATE,
        allowNull: false
      },
      updated_at: {
        type: Sequelize.DATE,
        allowNull: false
      }
    });
  },
  down: function(queryInterface, Sequelize) {
    return queryInterface.dropTable('friends');
  }
};

マイグレーションを実行すると複合主キーを持つ friends テーブルが作成されます。

image.png

コマンドまとめ

ここで紹介したコマンドの一覧です。

内容 コマンド
モデル+雛型作成 sequelize model:create --name [モデル名] --underscored --attributes [カラム名:データ型]
雛型作成 sequelize migration:generate --name [ファイルの接尾辞]
マイグレーション実行 sequelize db:migrate --env [対象の環境]
巻き戻し sequelize db:migrate:undo --env [対象の環境]

DB上級者向けのTIPS

誤ったマイグレーション操作のために sequelize db:migrate:undo ではどうにもならくなった場合は SequelizeMeta のレコードを直接削除する、追加するなどで適用履歴を操作することが可能です。
あくまで SequelizeMeta の履歴管理の仕組を把握している、かつSQLに長けた上級者向けの技です。