Node.jsの代表的なO/RMであるSequelizeの第四弾。
第1回ではインストールから基本的な利用方法、第2回ではSELECT文の使い方、第3回ではトランザクションを取り上げました。
今回は「マイグレーション」のお話です。 Sequelizeから提供されているCLIツールを利用すると、モデルの内容をコマンド一発でMySQLへ反映することができます。最初の1回目だけではなく運用開始後に差分を反映することもできます。他のフレームワークなどでも見かけますよね。Ruby on Railsで初めてこの手のツールを触ったときは感動したものですw
インストール
チュートリアル
事前準備や基本的な利用方法については第1回を参照ください。 blog.katsubemakito.net
CLIツール
package.jsonのあるディレクトリへ移動し追加でCLIツールをインストールします。-D
オプションは--save-dev
と同じ意味です。インストールが正常に完了するとnode_modules/.binの下にsequelize-cliコマンドが入ります。
$ npm install -D sequelize-cli
実際に動作するか確認します。今回は6.2.0が入りました。
$ npx sequelize-cli --version Sequelize CLI [Node: 14.17.0, CLI: 6.2.0, ORM: 6.6.5] 6.2.0
CLIの基本的な利用方法
テンプレを生成
一番最初にテンプレを自動生成してくれるコマンドを実行します。
$ npx sequelize-cli init
これで以下の4つのディレクトリとその中にいくつかのファイルが生成されます。
- config
- データベースへの接続方法を記録した設定ファイルの保管場所。中にいる「config.json」を編集します。
- models
- プロジェクトのすべてのモデルの保管場所。
- migrations
- マイグレーション実行時に利用するファイル置き場。
- seeders
- テーブルの新規作成後に初期データとして挿入するファイル置き場。シードファイル。
config/config.jsonを編集
MySQLへの接続情報を記入します。3つの環境が書かれていますが、これは自由に増減できます。ちなみにデフォルトではdevelopmentが採用されその他の環境に接続する場合は環境変数などで切り替えます。
{ "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" } }
なおこのファイルはJavaScriptではなく「JSON」です。JavaScriptのようにコメントを書いたり文字列をシングルコーテーション(')で囲うことはできません。同様に配列やオブジェクトの一番最後の要素の後ろに余計なカンマも許されません。文法のチェックが非常に厳しいのでご注意を。
最初のモデルを作成
model:generate
オプションを付けるとモデルのテンプレを自動生成してくれます。以下ではnameとemailのカラムを持ったUserモデルを作成します(改行していますが1行で書いてももちろんOKです)
$ npx sequelize-cli model:generate \ --name User \ --attributes name:string,email:string
以下の2つのファイルが生成されます。
- models/user.js
- モデルのテンプレです。基本的にここに処理を加筆していきます。
- migrations/20210809142244-create-user.js
- マイグレーション用のファイルです。MySQLへ反映する内容が書かれています。こちらもテーブル定義を変更する場合に必要に応じて編集します。
マイグレーション
先ほど生成されたマイグレーション用ファイルを元にモデルをMySQLへ反映します。
$ npx sequelize-cli db:migrate
実際に確認してみる
MySQLを覗くと2つのテーブルが作成されています。「SequelizeMeta」はマイグレーション管理用、「Users」が先ほど作成したモデルを反映したテーブルです。
mysql> show tables;
+--------------------------------+
| Tables_in_database_development |
+--------------------------------+
| SequelizeMeta |
| Users |
+--------------------------------+
Userモデルのテーブル構成も意図した通りの物になっていますね。
mysql> desc Users; +-----------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------+--------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | name | varchar(255) | YES | | NULL | | | email | varchar(255) | YES | | NULL | | | createdAt | datetime | NO | | NULL | | | updatedAt | datetime | NO | | NULL | | +-----------+--------------+------+-----+---------+----------------+
ちなみに「SequelizeMeta」にはマイグレーション時に利用したファイルが記録されます。これは単なる履歴ではなくマイグレーションの処理を行う際に利用されますので間違えて消さないよう気をつける必要があります。
mysql> select * from SequelizeMeta;
+-------------------------------+
| name |
+-------------------------------+
| 20210809142244-create-user.js |
+-------------------------------+
元に戻すには?
先ほど実行したマイグレーションを無かったことにできます。
$ npx sequelize-cli db:migrate:undo
MySQLを覗くと先ほど作成したUsersテーブルが消えて無くなり、「SequelizeMeta」から先ほど実行されたファイル名も消えました。
mysql> show tables; +--------------------------------+ | Tables_in_database_development | +--------------------------------+ | SequelizeMeta | +--------------------------------+ mysql> select * from SequelizeMeta; Empty set (0.00 sec)
別の環境にマイグレーションするには?
--env
オプションで環境を指定します。以下ではtest環境を指定しています。
$ npx sequelize-cli db:migrate --env test
環境変数NODE_ENV
に環境名をセットしてから実行する方法もありますが、うっかり設定したのを忘れて見当違いの環境を触る可能性があるので、恒常的に変更する場合に.bashrc/.zshrcなどに書いて利用するのがオススメです。
$ export NODE_ENV=test
モデルの利用方法
呼び出し方
作成したUserモデルの利用方法は至って簡単。
user = require('./models/user')
としても良いのですが、「models/index.js」をrequireした上で実際に利用したいモデル名をメソッドチェーンで書くと自動的に呼び出してくれます。一度にたくさんのモデルを使いたい場合もこれならシンプルに書けますね。
const models = require('./models') !(async ()=>{ await models.User.create({ name: 'Kawasaki', email: 'kawasaki@example.com' }) const rows = await models.User.findAll() rows.forEach(row => { console.log(`${row.name} ${row.email}`) }) })()
環境を変更する
デフォルトではconfog.jsonで指定した「development」環境を見ているわけですが、CLIツールと同様に環境変数NODE_ENV
に環境名をセットすることで変更することができます。
$ export NODE_ENV=test $ node foo.js
この処理は「models/index.js」の7行目に書かれていますので、必要に応じて書き換えることもできます。実際に利用する際にはdotenvを使った方が楽ちんですね。
const env = process.env.NODE_ENV || 'development';
初期データ
先ほど作成したUserテーブルに初期データを入れてみます。Sequelizeではseedと呼びます。
seedファイルを作成
まずはどういったデータを入れるのか定義するためのテンプレをコマンドで生成します。
$ npx sequelize-cli seed:generate --name demo-user
するとseedersディレクトリの下に以下のようなファイルが作成されます。
'use strict'; module.exports = { up: async (queryInterface, Sequelize) => { // ここに初期データを定義 }, down: async (queryInterface, Sequelize) => { // undoする際の処理を定義 } };
では先ほどのファイルに実際のデータを記述します。注意点としてはテーブル名は複数形の方を書く必要があるのと、タイムスタンプ機能を利用している場合はcreatedAtやupdatedAtなどもデータを定義する必要があります。
'use strict'; module.exports = { up: async (queryInterface, Sequelize) => { return queryInterface.bulkInsert('Users', [ {name: 'Kawasaki', email: 'kawasaki@example.com', createdAt: new Date(), updatedAt: new Date()}, {name: 'Honda', email: 'honda@example.com', createdAt: new Date(), updatedAt: new Date()}, {name: 'Yamaha', email: 'yamaha@example.com', createdAt: new Date(), updatedAt: new Date()}, {name: 'Suzuki', email: 'suzuki@example.com', createdAt: new Date(), updatedAt: new Date()} ]); }, down: async (queryInterface, Sequelize) => { return queryInterface.bulkDelete('Users', null, {}); } };
MySQLへ初期データを反映
コマンド一発です。
$ npx sequelize-cli db:seed:all
実際にMySQL上のテーブルを確認すると無事にINSERTされていました。
mysql> select * from Users; +----+----------+----------------------+---------------------+---------------------+ | id | name | email | createdAt | updatedAt | +----+----------+----------------------+---------------------+---------------------+ | 1 | Kawasaki | kawasaki@example.com | 2021-08-09 16:07:47 | 2021-08-09 16:07:47 | | 2 | Honda | honda@example.com | 2021-08-09 16:07:47 | 2021-08-09 16:07:47 | | 3 | Yamaha | yamaha@example.com | 2021-08-09 16:07:47 | 2021-08-09 16:07:47 | | 4 | Suzuki | suzuki@example.com | 2021-08-09 16:07:47 | 2021-08-09 16:07:47 | +----+----------+----------------------+---------------------+---------------------+
元に戻すには?
これもコマンド一発です。seedファイルのdown
で定義した内容がそのまま実行されます。
$ npx sequelize-cli db:seed:undo
MySQL上のテーブルを確認すると実行前の空の状態に戻っていました。
mysql> select * from Users; Empty set (0.00 sec)
追加でマイグレーションする
運用開始後にテーブルやカラムを追加したり削除したくなることがありますよね。そんな場合もマイグレーション機能で対応できます。
ここではサンプルとしてUsersテーブルにカラムを追加する作業を行います。
マイグレーションファイルを生成
model:generate
すると自動的にマイグレーション用のファイルも生成してくれましたが、このファイルだけを生成することもできます。
コマンド一発で生成できます。--name
オプションで指定する値は自由に付けることができます。
$ npx sequelize-cli migration:generate --name user-addcloumn
migrationsディレクトリの下に「20210810034653-user-addcloumn.js」という名前のファイルが作成されました。
編集する
以下のようなファイルが作成されるので、up内にマイグレーション時の処理を、down内にundoした際の処理を書いていきます。
生成直後のコード
'use strict'; module.exports = { up: async (queryInterface, Sequelize) => { // ここにカラムを追加する処理 }, down: async (queryInterface, Sequelize) => { // ここにundoした際の処理 // (つまりカラムを削除する) } };
カラム追加に対応したコード
今回はUsersテーブルにaddressとageという名前のカラムを追加します。
'use strict'; module.exports = { up: async (queryInterface, Sequelize) => { return Promise.all([ // Users.addressを追加 queryInterface.addColumn('Users', 'address', { type: Sequelize.STRING(128), after: 'email' // emailの後ろに追加(MySQLのみ) }), // Users.ageを追加 queryInterface.addColumn('Users', 'age', { type: Sequelize.INTEGER, after: 'name' // nameの後ろに追加(MySQLのみ) }), ]) }, down: async (queryInterface, Sequelize) => { // Users.address, Users.ageを削除 return Promise.all([ queryInterface.removeColumn('Users', 'address'), queryInterface.removeColumn('Users', 'age') ]); } };
利用できるメソッド
QueryInterfaceで用意されているメソッドが利用可能です。
以下はその一例です。
# | メソッド名 | 機能 |
---|---|---|
1 | createDatabase | データベースを作成 |
2 | dropDatabase | データベースを削除 |
3 | createTable | テーブルを作成 |
4 | dropTable | テーブルを削除 |
5 | renameTable | テーブル名を変更 |
6 | addColumn | カラムを追加 |
7 | changeColumn | カラムの属性を変更 |
8 | removeColumn | カラムを削除 |
9 | renameColumn | カラム名を変更 |
10 | addIndex | インデックスを追加 |
11 | removeIndex | インデックスを削除 |
マイグレーションを実行
コマンドは全く同じです。SequelizeがSequelizeMetaテーブルを参照し実行していないマイグレーションファイル内のupで定義した処理を実行してくれます。
$ npx sequelize-cli db:migrate
MySQLを確認するとaddressとageが追加されているのが確認できました。
mysql> desc Users; +-----------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------+--------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | name | varchar(255) | YES | | NULL | | | age | int(11) | YES | | NULL | | | email | varchar(255) | YES | | NULL | | | address | varchar(128) | YES | | NULL | | | createdAt | datetime | NO | | NULL | | | updatedAt | datetime | NO | | NULL | | +-----------+--------------+------+-----+---------+----------------+
元に戻す
こちらのコマンドも同じくundoを実行します。最後に実行したマイグレーションのdown内の処理を実行してくれます。
$ npx sequelize-cli db:migrate:undo
テーブルからも削除されているのが分かりますね。
mysql> desc Users; +-----------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------+--------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | name | varchar(255) | YES | | NULL | | | email | varchar(255) | YES | | NULL | | | createdAt | datetime | NO | | NULL | | | updatedAt | datetime | NO | | NULL | | +-----------+--------------+------+-----+---------+----------------+