マイグレーション
バージョン管理システム(例: Git)を使ってソースコードの変更を管理するのと同じように、マイグレーションを使ってデータベースへの変更を追跡できます。マイグレーションを使用すると、既存のデータベースを別の状態に移行したり、その逆に移行したりすることができます。これらの状態遷移は、新しい状態への移行方法と、古い状態に戻るために変更を元に戻す方法を記述したマイグレーションファイルに保存されます。
Sequelizeコマンドラインインターフェース(CLI)が必要です。CLIは、マイグレーションとプロジェクトのブートストラップをサポートしています。
Sequelizeにおけるマイグレーションは、2つの関数upとdownをエクスポートするjavascriptファイルであり、マイグレーションの実行方法と元に戻す方法を指示します。これらの関数は手動で定義しますが、手動で呼び出すことはありません。これらはCLIによって自動的に呼び出されます。これらの関数では、sequelize.queryやSequelizeが提供する他のメソッドを使用して、必要なクエリを実行するだけです。それ以上の特別な処理はありません。
CLIのインストール
Sequelize CLIをインストールするには
npm install --save-dev sequelize-cli
詳細については、CLI GitHubリポジトリを参照してください。
プロジェクトのブートストラップ
空のプロジェクトを作成するには、initコマンドを実行する必要があります
npx sequelize-cli init
これにより、次のフォルダが作成されます
- config: CLIがデータベースに接続する方法を指示する設定ファイルが含まれています
- models: プロジェクトのすべてのモデルが含まれています
- migrations: すべてのマイグレーションファイルが含まれています
- seeders: すべてのシードファイルが含まれています
設定
先に進む前に、CLIにデータベースへの接続方法を指示する必要があります。そのためには、デフォルトの設定ファイルconfig/config.jsonを開きましょう。これは次のようなものです
{
  "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"
  }
}
Sequelize CLIはデフォルトでmysqlを想定していることに注意してください。別のダイアレクトを使用している場合は、"dialect"オプションの内容を変更する必要があります。
次に、このファイルを編集して、正しいデータベースの資格情報とダイアレクトを設定します。オブジェクトのキー(例: "development")は、model/index.jsでprocess.env.NODE_ENV(未定義の場合は "development"がデフォルト値)を照合するために使用されます。
Sequelizeは、各ダイアレクトのデフォルトの接続ポートを使用します(たとえば、postgresの場合、ポート5432です)。別のポートを指定する必要がある場合は、"port"フィールドを使用します(config/config.jsにはデフォルトでは存在しませんが、簡単に追加できます)。
注:データベースがまだ存在しない場合は、db:createコマンドを呼び出すだけです。適切なアクセス権があれば、データベースが作成されます。
最初のモデル(とマイグレーション)の作成
CLIの設定ファイルが正しく設定されたら、最初のマイグレーションを作成する準備が整いました。簡単なコマンドを実行するだけです。
model:generateコマンドを使用します。このコマンドには2つのオプションが必要です
- name:モデルの名前;
- attributes:モデル属性のリスト。
Userという名前のモデルを作成しましょう。
npx sequelize-cli model:generate --name User --attributes firstName:string,lastName:string,email:string
これにより
- modelsフォルダーにモデルファイル- userが作成されます。
- migrationsフォルダーに- XXXXXXXXXXXXXX-create-user.jsのような名前のマイグレーションファイルが作成されます。
注:Sequelizeはモデルファイルのみを使用します。これはテーブルの表現です。一方、マイグレーションファイルは、CLIで使用される、そのモデル、より具体的にはそのテーブルの変更です。マイグレーションを、データベースの変更のコミットまたはログのように考えてください。
マイグレーションの実行
このステップまでは、データベースに何も挿入していません。最初のモデルUserに必要なモデルとマイグレーションファイルを作成しただけです。データベースに実際にテーブルを作成するには、db:migrateコマンドを実行する必要があります。
npx sequelize-cli db:migrate
このコマンドは、次のステップを実行します
- データベースにSequelizeMetaというテーブルがあることを確認します。このテーブルは、現在のデータベースでどのマイグレーションが実行されたかを記録するために使用されます
- まだ実行されていないマイグレーションファイルを探し始めます。これはSequelizeMetaテーブルを確認することで可能です。この場合、前のステップで作成したXXXXXXXXXXXXXX-create-user.jsマイグレーションを実行します。
- マイグレーションファイルで指定されたすべての列を持つUsersというテーブルを作成します。
マイグレーションの取り消し
これで、テーブルが作成され、データベースに保存されました。マイグレーションを使用すると、コマンドを実行するだけで古い状態に戻すことができます。
db:migrate:undoを使用できます。このコマンドは、最新のマイグレーションを元に戻します。
npx sequelize-cli db:migrate:undo
db:migrate:undo:allコマンドを使用してすべてのマイグレーションを元に戻すことで、初期状態に戻すことができます。--toオプションで名前を渡すことで、特定のマイグレーションに戻すこともできます。
npx sequelize-cli db:migrate:undo:all --to XXXXXXXXXXXXXX-create-posts.js
最初のシードの作成
デフォルトでいくつかのテーブルにデータを挿入したいとしましょう。前の例に続いて、Userテーブルのデモユーザーを作成することを検討できます。
すべてのデータマイグレーションを管理するには、シーダーを使用できます。シードファイルは、データベーステーブルにサンプルデータまたはテストデータを投入するために使用できる、データの変更です。
Userテーブルにデモユーザーを追加するシードファイルを作成しましょう。
npx sequelize-cli seed:generate --name demo-user
このコマンドは、seedersフォルダーにシードファイルを作成します。ファイル名はXXXXXXXXXXXXXX-demo-user.jsのようになります。マイグレーションファイルと同じup/downセマンティクスに従います。
次に、このファイルを編集して、Userテーブルにデモユーザーを挿入する必要があります。
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.bulkInsert('Users', [
      {
        firstName: 'John',
        lastName: 'Doe',
        email: '[email protected]',
        createdAt: new Date(),
        updatedAt: new Date(),
      },
    ]);
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.bulkDelete('Users', null, {});
  },
};
シードの実行
前のステップでシードファイルを作成しましたが、データベースにコミットされていません。そのためには、簡単なコマンドを実行します。
npx sequelize-cli db:seed:all
これにより、シードファイルが実行され、デモユーザーがUserテーブルに挿入されます。
注:シーダーの実行履歴は、マイグレーションとは異なり、SequelizeMetaテーブルを使用しません。この動作を変更したい場合は、Storageセクションをお読みください。
シードの取り消し
シーダーは、ストレージを使用している場合に取り消すことができます。これには2つのコマンドが使用できます
最新のシードを取り消す場合
npx sequelize-cli db:seed:undo
特定のシードを取り消す場合
npx sequelize-cli db:seed:undo --seed name-of-seed-as-in-data
すべてのシードを取り消す場合
npx sequelize-cli db:seed:undo:all
マイグレーションのスケルトン
次のスケルトンは、典型的なマイグレーションファイルを示しています。
module.exports = {
  up: (queryInterface, Sequelize) => {
    // logic for transforming into the new state
  },
  down: (queryInterface, Sequelize) => {
    // logic for reverting the changes
  },
};
migration:generateを使用してこのファイルを生成できます。これにより、マイグレーションフォルダーにxxx-migration-skeleton.jsが作成されます。
npx sequelize-cli migration:generate --name migration-skeleton
渡されたqueryInterfaceオブジェクトを使用して、データベースを変更できます。Sequelizeオブジェクトは、STRINGやINTEGERなどの使用可能なデータ型を格納します。関数upまたはdownはPromiseを返す必要があります。例を見てみましょう
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.createTable('Person', {
      name: Sequelize.DataTypes.STRING,
      isBetaMember: {
        type: Sequelize.DataTypes.BOOLEAN,
        defaultValue: false,
        allowNull: false,
      },
    });
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable('Person');
  },
};
次は、データベースで2つの変更を実行するマイグレーションの例です。自動的に管理されるトランザクションを使用して、すべての命令が正常に実行されるか、失敗した場合にロールバックされるようにします
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.sequelize.transaction(t => {
      return Promise.all([
        queryInterface.addColumn(
          'Person',
          'petName',
          {
            type: Sequelize.DataTypes.STRING,
          },
          { transaction: t },
        ),
        queryInterface.addColumn(
          'Person',
          'favoriteColor',
          {
            type: Sequelize.DataTypes.STRING,
          },
          { transaction: t },
        ),
      ]);
    });
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.sequelize.transaction(t => {
      return Promise.all([
        queryInterface.removeColumn('Person', 'petName', { transaction: t }),
        queryInterface.removeColumn('Person', 'favoriteColor', {
          transaction: t,
        }),
      ]);
    });
  },
};
次の例は、外部キーを持つマイグレーションの例です。参照を使用して外部キーを指定できます
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.createTable('Person', {
      name: Sequelize.DataTypes.STRING,
      isBetaMember: {
        type: Sequelize.DataTypes.BOOLEAN,
        defaultValue: false,
        allowNull: false,
      },
      userId: {
        type: Sequelize.DataTypes.INTEGER,
        references: {
          model: {
            tableName: 'users',
            schema: 'schema',
          },
          key: 'id',
        },
        allowNull: false,
      },
    });
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable('Person');
  },
};
次の例は、async/awaitを使用し、新しい列に一意のインデックスを、手動で管理されるトランザクションで作成するマイグレーションの例です
module.exports = {
  async up(queryInterface, Sequelize) {
    const transaction = await queryInterface.sequelize.transaction();
    try {
      await queryInterface.addColumn(
        'Person',
        'petName',
        {
          type: Sequelize.DataTypes.STRING,
        },
        { transaction },
      );
      await queryInterface.addIndex('Person', 'petName', {
        fields: 'petName',
        unique: true,
        transaction,
      });
      await transaction.commit();
    } catch (err) {
      await transaction.rollback();
      throw err;
    }
  },
  async down(queryInterface, Sequelize) {
    const transaction = await queryInterface.sequelize.transaction();
    try {
      await queryInterface.removeColumn('Person', 'petName', { transaction });
      await transaction.commit();
    } catch (err) {
      await transaction.rollback();
      throw err;
    }
  },
};
次の例は、条件付きで複数のフィールドで構成される一意のインデックスを作成するマイグレーションの例です。これにより、リレーションを複数回存在させることができますが、条件を満たすのは1つだけです
module.exports = {
  up: (queryInterface, Sequelize) => {
    queryInterface
      .createTable('Person', {
        name: Sequelize.DataTypes.STRING,
        bool: {
          type: Sequelize.DataTypes.BOOLEAN,
          defaultValue: false,
        },
      })
      .then((queryInterface, Sequelize) => {
        queryInterface.addIndex('Person', ['name', 'bool'], {
          indicesType: 'UNIQUE',
          where: { bool: 'true' },
        });
      });
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable('Person');
  },
};
.sequelizercファイル
これは特別な設定ファイルです。通常はCLIに引数として渡す以下のオプションを指定できます。
- env: コマンドを実行する環境
- config: 設定ファイルへのパス
- options-path: 追加オプションを含むJSONファイルへのパス
- migrations-path: マイグレーションフォルダへのパス
- seeders-path: シーダーフォルダへのパス
- models-path: モデルフォルダへのパス
- url: 使用するデータベース接続文字列。--configファイルを使用する代替手段
- debug: 利用可能な場合、様々なデバッグ情報を表示
使用できるシナリオの例
- migrations、- models、- seeders、または- configフォルダへのデフォルトパスを上書きしたい場合。
- config.jsonを- database.jsonのように別の名前に変更したい場合。
その他にも多くの場合に使用できます。このファイルをカスタム設定に使用する方法を見ていきましょう。
まず、プロジェクトのルートディレクトリに .sequelizerc ファイルを作成し、以下の内容を記述します。
// .sequelizerc
const path = require('path');
module.exports = {
  config: path.resolve('config', 'database.json'),
  'models-path': path.resolve('db', 'models'),
  'seeders-path': path.resolve('db', 'seeders'),
  'migrations-path': path.resolve('db', 'migrations'),
};
この設定により、CLIに対して以下のことを指示します。
- 設定に config/database.jsonファイルを使用する。
- モデルフォルダとして db/modelsを使用する。
- シーダーフォルダとして db/seedersを使用する。
- マイグレーションフォルダとして db/migrationsを使用する。
動的な設定
設定ファイルはデフォルトでは config.json という名前のJSONファイルです。しかし、環境変数にアクセスしたり、設定を決定するために他のコードを実行するなど、動的な設定が必要になる場合があります。
幸いなことに、Sequelize CLIは .json ファイルと .js ファイルの両方を読み込むことができます。これは .sequelizerc ファイルで設定できます。エクスポートされたオブジェクトの config オプションとして、 .js ファイルへのパスを指定するだけです。
const path = require('path');
module.exports = {
  config: path.resolve('config', 'config.js'),
};
これで、Sequelize CLIは設定オプションを取得するために config/config.js をロードします。
config/config.js ファイルの例
const fs = require('fs');
module.exports = {
  development: {
    username: 'database_dev',
    password: 'database_dev',
    database: 'database_dev',
    host: '127.0.0.1',
    port: 3306,
    dialect: 'mysql',
    dialectOptions: {
      bigNumberStrings: true,
    },
  },
  test: {
    username: process.env.CI_DB_USERNAME,
    password: process.env.CI_DB_PASSWORD,
    database: process.env.CI_DB_NAME,
    host: '127.0.0.1',
    port: 3306,
    dialect: 'mysql',
    dialectOptions: {
      bigNumberStrings: true,
    },
  },
  production: {
    username: process.env.PROD_DB_USERNAME,
    password: process.env.PROD_DB_PASSWORD,
    database: process.env.PROD_DB_NAME,
    host: process.env.PROD_DB_HOSTNAME,
    port: process.env.PROD_DB_PORT,
    dialect: 'mysql',
    dialectOptions: {
      bigNumberStrings: true,
      ssl: {
        ca: fs.readFileSync(__dirname + '/mysql-ca-main.crt'),
      },
    },
  },
};
上記の例では、カスタムダイアレクトオプションを設定に追加する方法も示しています。
Babelの使用
マイグレーションとシーダーでよりモダンな構文を有効にするには、babel-register をインストールし、.sequelizerc の先頭でそれを require するだけです。
npm i --save-dev babel-register
// .sequelizerc
require('babel-register');
const path = require('path');
module.exports = {
  config: path.resolve('config', 'config.json'),
  'models-path': path.resolve('models'),
  'seeders-path': path.resolve('seeders'),
  'migrations-path': path.resolve('migrations'),
};
もちろん、結果は(.babelrc ファイルのような)Babelの設定に依存します。詳細については、babeljs.io を参照してください。
セキュリティに関するヒント
設定には環境変数を使用してください。パスワードなどの機密情報は、ソースコードの一部にすべきではなく(特にバージョン管理にコミットすべきではありません)。
ストレージ
使用できるストレージには、sequelize、json、none の3種類があります。
- sequelize: マイグレーションとシーダーをSequelizeデータベースのテーブルに保存します。
- json: マイグレーションとシーダーをjsonファイルに保存します。
- none: マイグレーション/シーダーを保存しません。
マイグレーションストレージ
デフォルトでは、CLIは実行された各マイグレーションのエントリを含む SequelizeMeta という名前のテーブルをデータベースに作成します。この動作を変更するには、設定ファイルに追加できる3つのオプションがあります。migrationStorage を使用すると、マイグレーションに使用するストレージの種類を選択できます。json を選択した場合、migrationStoragePath を使用してファイルのパスを指定できます。指定しない場合、CLIは sequelize-meta.json ファイルに書き込みます。データベースに情報を保持したいが、sequelize を使用し、異なるテーブルを使用したい場合は、migrationStorageTableName を使用してテーブル名を変更できます。また、migrationStorageTableSchema プロパティを指定することで、SequelizeMeta テーブルに異なるスキーマを定義できます。
{
  "development": {
    "username": "root",
    "password": null,
    "database": "database_development",
    "host": "127.0.0.1",
    "dialect": "mysql",
    // Use a different storage type. Default: sequelize
    "migrationStorage": "json",
    // Use a different file name. Default: sequelize-meta.json
    "migrationStoragePath": "sequelizeMeta.json",
    // Use a different table name. Default: SequelizeMeta
    "migrationStorageTableName": "sequelize_meta",
    // Use a different schema for the SequelizeMeta table
    "migrationStorageTableSchema": "custom_schema"
  }
}
注意: none ストレージはマイグレーションストレージとして推奨されません。使用する場合は、どのマイグレーションが実行されたか、または実行されなかったかの記録がないことの意味を理解してください。
シーダーストレージ
デフォルトでは、CLIは実行されたシードを保存しません。この動作を変更する場合(!)、設定ファイルで seederStorage を使用してストレージタイプを変更できます。json を選択した場合、seederStoragePath を使用してファイルのパスを指定できます。指定しない場合、CLIは sequelize-data.json ファイルに書き込みます。データベースに情報を保持したい場合、sequelize を使用して、seederStorageTableName を使用してテーブル名を指定できます。指定しない場合は、デフォルトで SequelizeData になります。
{
  "development": {
    "username": "root",
    "password": null,
    "database": "database_development",
    "host": "127.0.0.1",
    "dialect": "mysql",
    // Use a different storage. Default: none
    "seederStorage": "json",
    // Use a different file name. Default: sequelize-data.json
    "seederStoragePath": "sequelizeData.json",
    // Use a different table name. Default: SequelizeData
    "seederStorageTableName": "sequelize_data"
  }
}
設定接続文字列
データベースを定義する設定ファイルを使用する --config オプションの代替として、--url オプションを使用して接続文字列を渡すことができます。例:
npx sequelize-cli db:migrate --url 'mysql://root:password@mysql_host.com/database_name'
npmで package.json スクリプトを使用する場合は、フラグを使用する際にコマンドに -- を追加するようにしてください。例:
// package.json
...
  "scripts": {
    "migrate:up": "npx sequelize-cli db:migrate",
    "migrate:undo": "npx sequelize-cli db:migrate:undo"
  },
...
コマンドは npm run migrate:up -- --url <url> のように使用します。
プログラムによる使用
Sequelizeには、マイグレーションタスクの実行とロギングをプログラムで処理するためのumzugという姉妹ライブラリがあります。