Nodeアプリの設定系モジュールいろいろ

Node(node.js)でwebアプリを開発するにあたって、DBの情報やSession keyのsalt, OAuthのsecret_keyなどの設定をどうするかを悩み中。考えているのは

  • アプリケーションのコードに設定値を埋め込むようなことは絶対にしたくない
  • ということは設定用のファイルを用意してそこを読み込む形に?
  • 開発環境と本番環境で設定が変わるので上手く切り替えられるようにしたい
  • npmで普通に入れられる軽量なライブラリで実現出来れば良い

…で、どんなのがあるか調べてみたところ結構色々あったので以下の6つくらいを実際に入れて使って試してみた。

cjson

コメント付きで記述したJSON形式なファイルを使える、とのこと。

/*
 * This is my app configuration file.
 * 
 */
{
	"host": "localhost",
	// app is listening on this port
	"port": 8888
}

といった設定ファイルに対し

(function () {
    var cjson = require('cjson');
    var conf = cjson.load('./conf/hoge.json');
    console.log(conf);
}());

といった形で読み取れる。
…が、なんか自前でdecommentという処理をしていて、しかもそのロジックが間違っているようだ。この方式自体がナンセンスに感じるし、これは正直ちょっとイケてない。

config

コマンドライン引数で読み込む設定ファイルを指定する、という方式。

(function () {
    var config = require('config');
    var conf = config('hoge', {
        host: 'example.com'
    });
    console.log(conf);
}());

といったNodeスクリプトに対し

{
    "hoge": {
        "host": "localhost",
        "port": 8888
    },
    "fuga": {
        "foo": "bar"
    }
}

のような設定ファイルを用意して

$ node example.js -config ./conf/hoge.json
{ host: 'localhost', port: 8888 }

という具合に"-config"オプションに続き読み込むファイルを指定。設定は各モジュールごとに設定値を用意しておく形で、config関数の第1引数でどのモジュールの設定値を読み込むか指定、第2引数でデフォルト設定値を指定する。上記例はconfig関数で"hoge"を指定しているので"fuga"に関わる部分は無視され(メモリには読み込まれてるけど)、"hoge"に該当する"host", "port"の設定値がデフォルト値を上書きして返すことになる。
設定ファイルはjson, yaml、そしてmodule.exportに値を設定するjsファイルが使用可能。拡張子で判別しているようだ。

hoge:
  host: localhost
  port: 8888
/*
 * block comment
 */
module.exports = {
    // line comment
    hoge: {
        host: "localhost",
        port: 8888
    }
};

JavaScript形式はrequireされて利用されることになるので、普通にコメントなども書ける。この形式がベストかも。
ちなみに現在のバージョン0.2.9をnpm1.0.8でinstallすると警告が出てしまう。

$ npm --version
1.0.8
$ npm install config
npm WARN config@0.2.9 package.json: 'modules' object is deprecated
config@0.2.9 ./node_modules/config
├── eyes@0.1.6
├── yaml@0.2.1
├── vows@0.5.8
└── underscore@1.1.4

config/package.jsonの形式がちょっと古いのかな…? 特に大きな害は無いと思うけどちょっと気持ち悪い。pull req送ってみた方が良いかしら。

confu

超単純に、指定したパスにあるJSONファイルを読み取って返す、というもののようだ。

(function () {
    var confu = require('confu');
    var conf = confu('.', 'conf', 'hoge.json');
    console.log(conf);
}());

と引数に設定ファイルのパスを渡してやる。

$ cat conf/hoge.json
{
    "hoge": {
        "host": "localhost",
        "port": 8888
    },
    "fuga": {
        "foo": "bar"
    }
}
$ node example.js
{ hoge: { host: 'localhost', port: 8888 },
  fuga: { foo: 'bar' },
  paths:
   { root: '/Users/sugyan/temp/config/conf',
     config: '/Users/sugyan/temp/config/conf/hoge.json' } }

中ではパス解決してJSON.parseしてpathsを書き加えて返す、というだけのもの。簡単なのはコレでも良いのかも。

jsonconfig

非同期型。複数のJSONファイルに書いた設定をmergeして使うのに良さそう。

(function () {
    var jsonconfig = require('jsonconfig');
    console.log(jsonconfig);
    jsonconfig.load([
        './conf/hoge.json',
        './conf/fuga.json'
    ], function (err) {
        if (err) { throw err; }
        console.log(jsonconfig);
    });
}());
$ cat conf/hoge.json
{
    "hoge": {
        "host": "localhost",
        "port": 8888
    },
    "fuga": {
        "foo": "bar"
    }
}
$ cat conf/fuga.json
{
    "hoge": {
        "host": "example.com",
        "port": 8888
    },
    "fuga": {
        "foo": "baz"
    }
}
$ node example.js
{ load: [Function] }
{ load: [Function],
  hoge: { host: 'example.com', port: 8888 },
  fuga: { foo: 'baz' } }

require直後はload関数を持つだけのオブジェクトで、第1引数に読み込むJSONファイルのパスを配列で、第2引数にコールバックを渡してloadを呼ぶ。コールバックが呼ばれるときには、指定したファイルを読み込んでマージされた値がjsonconfigオブジェクトに設定されているのでそこから値を取得できる。
JSON縛りで、環境による切り替えがしにくいけれど、悪くはないかも。

nconf

ファイル以外にもKVSなどから読み込んだり逆に書き込んだりも出来る多機能ライブラリ。
普通にJSONファイルから読み込む場合は以下のようなかんじ。同期読み込み、非同期読み込みどちらも可能。

(function () {
    var nconf = require('nconf');
    nconf.use('file', {
        file: './conf/hoge.json'
    });
    // async
    nconf.load(function (err, conf) {
        if (err) { throw err; }
        console.log(conf);
    });
    // sync
    nconf.load();
    console.log(nconf.store.store);
}());

引数でコールバックを渡さずにloadを呼ぶと中でloadSyncを呼ぶことになるらしい。fileを使用している場合loadSyncでは読み込んだデータをparseしてthis.storeに格納し、parseする前の生データを返す。
設定値を書き出して保存することもできるので

(function () {
    var nconf = require('nconf');
    nconf.use('file', {
        file: './conf/hoge.json'
    });

    nconf.load(function (err, conf) {
        if (err) { throw err; }

        console.log(conf);
        conf.piyo = new Date();
        nconf.save(conf, function (err) {
            if (err) { throw err; }
            console.log('saved.');
        });
    });
}());

のようにすると毎回実行するたびに"piyo"の値が最新の時刻に上書きされて"hoge.json"が更新される。
デフォルトではJSON形式に対応しているものの、formatを変更することもできるので他の形式にも対応させることができる。

(function () {
    var nconf = require('nconf');
    nconf.use('file', {
        file: './conf/hoge.js',
        format: {
            parse: function (str) {
                return require(nconf.store.file);
            },
            stringify: JSON.stringify
        }
    });

    nconf.load(function (err, conf) {
        if (err) { throw err; }

        console.log(conf);
        conf.piyo = new Date();
        nconf.store.file = './conf/hoge.json';
        nconf.save(conf, function (err) {
            if (err) { throw err; }
            console.log('saved.');
        });
    });
}());

この例だとload時にJavaScript形式のファイルをrequireして設定値オブジェクトを取得、書き出すときはJSON.stringifyで別ファイルに書き出している。
KVSのRedisを使っての読み書きもサポートされている。

(function () {
    var nconf = require('nconf');
    nconf.use('redis');
    nconf.set('hoge', { fuga: 'piyo' }, function (err) {
        if (err) { throw err; }
        nconf.get('hoge', function (err, result) {
            if (err) { throw err; }
            console.log(result);
            nconf.store.redis.quit();
        });
    });
}());

キレイに作られているので、get, set, loadなどのメソッドをサポートするモジュールを用意することでmongodbを使うものとか自分で好きなstorageを使用するものを用意することが出来そうな気がする。

node-config

決められたパスに設定ファイルを置いておき、host名を使って環境による切り替えをするモジュール。

(function () {
    var node_config = require('node-config');
    node_config.initConfig(function (err) {
        if (err) { throw err; }
        console.log(node_config.hoge);
        console.log(node_config.foo);
    });
}());

デフォルトではカレントディレクトリ以下のconf/common.jsをまず読み、その後<hostname>.jsにある設定で上書きする。いずれもexports.confを使用する。

$ cat conf/common.js
exports.conf = {
    hoge: 'fuga',
    foo: ['bar', 'baz']
};
$ cat conf/sugyan.local.js
exports.conf = {
    hoge: 'piyo'
};
$ node example.js
piyo
[ 'bar', 'baz' ]

initConfig関数の第2引数でhost名を明示指定することもできる。ローカル開発環境と本番サーバでの設定切り分けには良いと思う。

まとめ

結局どれが良いかはアプリケーションの特性や規模にもよると思うので一概には言えないけど、個人的にはJSON形式に縛られたくないので使うとしたらconfig, nconf, node-configあたりかなぁ。基本的に起動時に読み込むだけだと思うので同期/非同期はそれほどこだわる必要は無い気がする。