まるまるこふこふ

数々の次元が崩壊し、全ての生命が塵と化すのを見てきた。私ほどの闇の心の持ち主でも、そこには何の喜びも無かった。

Node.js の ActiveRecord ライブラリ Knex.jsを使う

今日の知見です〜。

Node.js からActiveRecordを用いてRDBMSにアクセスするに辺り、 knex.jsが便利でした。

var knex = require('knex')({
  client: 'mysql',
  connection: {
    host     : '127.0.0.1',
    user     : 'your_database_user',
    password : 'your_database_password',
    database : 'myapp_test'
  },
  pool: {
    min: 0,
    max: 7
  }
});

knex
.select('*')
.from('test')
.where('id', id)
.then(function(rows) {
  console.log(rows);
})
.catch(function(err) {
  console.log(err);
});

雑にサンプル書いたけど、コネクションプーリングをknex側でやってくれるのが良い。 インターフェイスはcallbackでもPromiseでも選べる(上のサンプルはPromiseで書いてる)

selectを2回発行するときとか

javascriptの非同期に慣れていないとよくやってしまう失敗だが、 1回目のselect の結果を受けて2回目のselect を発行するとき、

var user_id;
knex
.select('user_id')
.from('user')
.where('id', id)
.then(function(rows) {
  user_id = rows[0].user_id;
})
.catch(function(err) {
  console.log(err);
});

knex
.select('*')
.from('detail')
.where('user_id', user_id)
.then(function(rows) {
  console.log(rows);
})
.catch(function(err) {
  console.log(err);
});

user_id を取得してdetail テーブルからデータを引くコードだが、上記のコードはうまく動かない。 2つのselect 文が非同期に実行されるので、user_id を取得してから detail に select される保証はない。

次のように1つめのselect文のコールバックの中で2つめのselect文を呼べば select文の発行順番が保証される。

var user_id;
knex
.select('user_id')
.from('user')
.where('id', id)
.then(function(rows) {
  user_id = rows[0].user_id;
  knex
  .select('*')
  .from('detail')
  .where('user_id', user_id)
  .then(function(rows) {
    console.log(rows);
  })
  .catch(function(err) {
    console.log(err);
  });
})
.catch(function(err) {
  console.log(err);
});

コールバックがネストするので非常にコードが読みにくい。select文を発行するたびに ネストしないといけないのか?Promise はこうした callback地獄を解決するための手段だ。

var user_id;
knex
.select('user_id')
.from('user')
.where('id', id)
.then(function(rows) {
  user_id = rows[0].user_id;
  return knex
  .select('*')
  .from('detail')
  .where('user_id', user_id)
})
.then(function(rows) {
  console.log(rows);
})
.catch(function(err) {
  console.log(err);
});

1つめのthen で user テーブルへのselectが実行され、2つめのthen で detail テーブルへのselect が実行される。 両者のselect 文のどちらかでエラーが発生すれば最後のcatch でエラーが拾われる。 わかりやすい。

DBへの接続をシングルトンで保持する。

複数のライブラリからknexを呼ぶ場合、各ライブラリでknexを初期化すると その文だけDBへのコネクションが増えるので、knex インスタンスを1つに保持しつつ、 複数のライブラリからknex を使用したい。 そのような場合、シングルトンなクラスを作る必要がある。

Node.js の require の仕組みはシングルトンを非常に作りやすい。

// knex-singleton.js
var knex = require('knex')({
  client: 'mysql',
  connection: {
    host     : '127.0.0.1',
    user     : 'your_database_user',
    password : 'your_database_password',
    database : 'myapp_test'
  },
  pool: {
    min: 0,
    max: 7
  }
});
module.exports = knex;

このようなモジュールを作っておき、各ライブラリから

var knex = require('./knex-singleton');

knex.select('*').from('user').then(function(rows){ console.log(rows); });

こんな感じで使える。knex-singleton が何回呼び出されようが、knex インスタンスは常に1つになる。