Sequelize を使って Express サーバから MySQL DB を操作してみる

Express サーバから MySQL DB とのやり取りを行おうと思い、Node.js で使える良い O/R マッパーがないか調べてみたところ、Sequelize というモノがよく使われているようだったので試してみた。

インストール

以下の公式ドキュメントに沿って、まずは Sequelize をインストールする。

Sequelize と同時に、利用する DB に合わせたパッケージもインストールする必要がある。今回は MySQL を使用するので mysql2 というパッケージを入れることになる。

$ npm install -S sequelize mysql2

単体で使ってみる

まずは Express サーバに組み込まず、単独の Node.js スクリプトとして使ってみる。

const Sequelize = require('sequelize');

// 接続先情報
const host     = 'my-sql-server.com';
const database = 'my_db';
const username = '【ユーザ名】';
const password = '【パスワード】';

// 接続開始
const sequelize = new Sequelize(database, username, password, {
  host: host,
  dialect: 'mysql',
  operatorsAliases: false,
  pool: {
    min: 0,
    max: 5,
    acuire: 30000,
    idle  : 10000
  }
});

// 接続確認用
sequelize.authenticate()
  .then(()       => { console.log('Success test connection');        })
  .catch((error) => { console.log('Failure test connection', error); });

// users テーブルに対応するモデルを作成する
const UserModel = sequelize.define('users', {                                                               // CREATE TABLE 文で指定した内容は大体以下のような感じ
  id       : { field: 'id'        , type: Sequelize.INTEGER(11) , primaryKey: true, autoIncrement: true },  // INT(11)       NOT NULL  PRIMARY KEY  AUTO_INCREMENT
  userName : { field: 'user_name' , type: Sequelize.STRING(100) , allowNull: false                      },  // VARCHAR(100)  NOT NULL
  address  : { field: 'address'   , type: Sequelize.STRING(500)                                         },  // VARCHAR(500)  DEFAULT NULL
  createdAt: { field: 'created_at', type: Sequelize.DATE                                                },  // DATETIME      NOT NULL  DEFAULT CURRENT_TIMESTAMP
  updatedAt: { field: 'updated_at', type: Sequelize.DATE                                                }   // DATETIME      NOT NULL  DEFAULT CURRENT_TIMESTAMP  ON UPDATE CURRENT_TIMESTAMP
});

// 全件取得してみる
UserModel.findAll()
  .then((result) => {
    console.log('データ取得', result.dataValues);
  });

こんなスクリプト sequelize-test.js を作成し、$ node sequelize-test.js で実行してみよう。SELECT * FROM users; 相当の SQL が実行され、データが取得できれば OK だ。

DB 接続に関するところは特段説明も要らないだろう。接続先情報を書いて渡してやれば良い。プールの設定などもできるので、この辺を調整したい場合は公式のドキュメントを読む。

モデルの定義

DB テーブルに対応するモデルを作成するのが、sequelize.define() 部分。ココで、DB 上はスネークケースで作成しているフィールド名を、キャメルケースのプロパティ名に直している。

const UserModel = sequelize.define('【DB テーブル名】', {
  // +--- モデルのプロパティ        +--- フィールド定義 : 型を示す専用のメソッドを使う
  // |         +--- DB カラム名     |                             +--- primaryKey 指定などがあれば行える
  // |         |                    |                             |
  id       : { field: 'id'        , type: Sequelize.INTEGER(11) , primaryKey: true, autoIncrement: true },
  userName : { field: 'user_name' , type: Sequelize.STRING(100) , allowNull: false                      },
  address  : { field: 'address'   , type: Sequelize.STRING(500)                                         },
  createdAt: { field: 'created_at', type: Sequelize.DATE                                                },
  updatedAt: { field: 'updated_at', type: Sequelize.DATE                                                }
});

DB 側と同じケースで命名していれば、このような変換は要らず、userName: { type: Sequelize.STRING } のように短く書いたりも出来る。詳しくは API リファレンスを参照しよう。

CRUD 操作してみる

上の例では Model#findAll() というメソッドを使って、users テーブルの全データを取得した。このように、Promise でデータが送り返されてくる作りだ。

以下に UserModel の場合の基本的な CRUD 操作のコードを書いておく。

// SELECT : 条件を指定して複数件取得する
UserModel.findAll({
  where: {
    address: '東京'
  }
})
  .then((result) => {
    // 結果の配列データは result.dataValues が持っている
  });

// SELECT : ID (キー) を指定して1件取得する
UserModel.findById(id)
  .then((result) => {
    // 結果のオブジェクトは result.dataValues が持っている
  });

// INSERT : 新規追加
//   id は自動採番させるため指定しない (指定するとその内容で INSERT される)
//   created_at・updated_at も DB 側に自動更新させるため未指定
UserModel.create({
  userName: '山田 太郎',
  address : '北海道'
})
  .then((result) => {
    // 新規追加できた結果オブジェクトは result.dataValues が返してくれる
  });

// UPDATE : 更新
UserModel.update({
  // 更新したい Key・Value を指定する
  userName: '田中 邦衛',
  address : '沖縄'
}, {
  // 更新対象を指定する
  where: {
    id: 29
  },
  // 更新するフィールド名を指定する
  fields: [
    'userName',
    'address'
  ]
})
  .then(() => { });

// DELETE : 削除
UserModel.destroy({
  // 削除対象を指定する
  where: {
    id: 42
  }
})
  .then(() => { });

他にも findOrCreateupsert など、便利なメソッドが多数用意されている。

Express サーバに組み込んでみる

ココまでで Sequelize を使った CRUD 操作はできるようになったと思うので、Express サーバに組み込んでみる。

といっても、コントローラでやることとしては、リクエストデータからパラメータを拾い上げて、それを元に Model.findAll() なり Model.update() なりの引数にセットしていくだけだ。結果を受け取ったらそれをレスポンスにセットすれば良いだけ。

モデルをどう用意するか、についてだが、以下のような共通クラスを用意するのが良いと思う。

/* app/models/models.js */

const Sequelize = require('sequelize');

// 接続情報 (省略)
// 接続開始 (省略)
const sequelize = new Sequelize(database, username, password, {
  host: host,
  dialect: 'mysql',
  operatorsAliases: false
});

// モデルを定義する
const Models = {};
// 各モデルを設定する
Models.User = require('./user-model')(sequelize);
Models.Product = require('./product-model')(sequelize);  // user-model.js 的なファイルがあるテイ
// Sequelize 本体を設定する
Models.sequelize = sequelize;
Models.Sequelize = Sequelize;
// エクスポートする
module.exports = Models;
/* app/models/user-model.js */

// 型定義用に require しておく
const Sequelize = require('sequelize');

module.exports = (sequelize) => {
  const UserModel = sequelize.define('users', {                                                                // CREATE TABLE 文で指定した内容は大体以下のような感じ
    id       : { field: 'id'        , type: Sequelize.INTEGER(11) , primaryKey: true, autoIncrement: true },  // INT(11)       NOT NULL  PRIMARY KEY  AUTO_INCREMENT
    userName : { field: 'user_name' , type: Sequelize.STRING(100) , allowNull: false                      },  // VARCHAR(100)  NOT NULL
    address  : { field: 'address'   , type: Sequelize.STRING(500)                                         },  // VARCHAR(500)  DEFAULT NULL
    createdAt: { field: 'created_at', type: Sequelize.DATE                                                },  // DATETIME      NOT NULL  DEFAULT CURRENT_TIMESTAMP
    updatedAt: { field: 'updated_at', type: Sequelize.DATE                                                }   // DATETIME      NOT NULL  DEFAULT CURRENT_TIMESTAMP  ON UPDATE CURRENT_TIMESTAMP
  });
  return UserModel;
};

このようなモデル生成用のファイル models.js をベースに、テーブルごとに user-model.jsproduct-model.js とファイルを分けておくと管理しやすいかと。

で、コントローラでは models.jsrequire() して使う。ルーティング部分は省略するが、リクエストとレスポンスを引数で受け取っているテイ。

/* app/controllers/users-controller.js */

// Models を読み込む
const Models = require('../models/models');

class UsersController {
  /** 検索するアクション */
  findAll(req, res) {
    // オプションの雛形を作っておく
    const options = {
      where: {}
    };
    
    // リクエストで address パラメータを指定されていれば、絞り込み検索のためのオプションとして追加する
    if(req.query['address']) {
      options.where['address'] = req.query['address'];
    }
    
    // User モデルを使用する
    Models.User.findAll(options)
      .then((result) => {
        // 200:OK と、JSON 形式の結果データ dataValues を返す
        res.status(200);
        res.json(result.dataValues);
      })
      .catch((error) => {
        // 404:NotFound と、エラー情報を返す
        res.status(404);
        res.json(error);
      });
  }
}

こんな風に Express サーバを作っておけば、http://localhost/users で全件、http://localhost/users?address=Tokyo で絞り込み検索した結果が受け取れる、API サーバの出来上がり。

SELECT の際の条件指定や、INSERT・UPDATE の際のモデル構築処理などは、コントローラから外出しして管理していくと良いだろう。


以上。DB 操作がかなり簡単になったので、Sequelize オススメ!