npmにパッケージをたくさん公開することを今年の目標にしました。公開するからには使ってもらえるようにしたいので、Qiitaに記事を書くところまでを目標にしたいです。
まずは第1弾として設定ファイルを読み込むためのライブラリを公開したので、どんなライブラリなのかを解説します。
目的
Node.jsで作成しているWebアプリケーションで、設定ファイルをJSやJSONのような頭使う形式1ではなく、YAMLで書きたい。ファイル内にはデータベースのパスワードやJWTのsecretなど、他人に見られては困るものもある。暗号化すればいいけど、どの設定値が暗号化されていて、どの設定値はされていないのか、明確なルールを作りたい。暗号化には基本的に公開鍵暗号を使いたいが、環境によっては別の方法を使いたいこともあるので、変更できるようにしたい。あと環境変数も使いたい。
こんな要望を一つのパッケージで実現したかったので、作ってみました。
URL
npm: https://www.npmjs.com/package/nobushi-config
GitHub: https://github.com/kou64yama/nobushi-config
使い方
npmパッケージなので、下記のコマンドでインストールできます。
npm i -S nobushi-config
設定ファイルはYAMLかJSONで書きます。JSONはおまけで対応しているだけで、YAML使った方がいいと思います。
# config/default.yml
server:
host: localhost
port: 3000
datasource:
url: postgresql://localhost:5432/sample
username: sample
password: secret
基本的な使い方は、nobushi-config
をインポートするだけです。
// main.js
import config from 'nobushi-config';
console.log(config.server.host); // => localhost
設定ファイルのルール
読み込み順序
設定ファイルは環境変数NODE_CONFIG_DIR
で設定されたディレクトリか、設定されていなければプロセスの相対パスで./config
にあるものを読み込みます。
default.yml
、${NODE_ENV}.yml
(環境変数NODE_ENV
が設定されていれば)の順でファイルを読み込みます(JSONの場合は拡張子を.json
に読み替えてください)。同じキーがあれば、後から読み込んだもので上書きします。
オブジェクトの扱い
YAMLやJSONのオブジェクトは何重にもネストして記述することができますが、nobushi-config内部ではすべてフラットな構造に変換されます。
例えば、
server:
host: localhost
port: 3000
という設定は、nobushi-config内部では
server.host: localhost
server.port: 3000
と解釈されます。
インポートしたオブジェクトからアクセスする際には、このフラットな構造を元のネストされたオブジェクトに復元します。
この仕様は
foo:
bar:
baz:
qux: foobarbazqux
みたいな設定を
foo.bar.baz.qux: foobarbazqux
と書けるようにしたかったからです。
配列の扱い
上記仕様により、配列の扱いには注意しなければなりません。
例えば
api:
- https://api1.example.com
- https://api2.example.com
- https://api3.example.com
のような設定は
api.0: https://api1.example.com
api.1: https://api2.example.com
api.2: https://api3.example.com
と解釈されます。インポートした際もオブジェクトになっています。
console.log(config.api);
// {
// '0': 'https://api1.example.com',
// '1': 'https://api2.example.com',
// '2': 'https://api3.example.com'
// }
${NODE_ENV}.yml
で上書きする際にも配列全体を上書きするようなことはできないので、
# config/default.yml
api:
- https://api1.example.com
- https://api2.example.com
- https://api3.example.com
# config/production.yml
api:
- https://api1.example.com/v2
- https://api2.example.com/v2
というように書いても、3つ目の要素がなくなることはありません。
api:
- https://api1.example.com/v2
- https://api2.example.com/v2
- https://api3.example.com
この配列に関する仕様は望ましいものではありませんので、今後のバージョンアップで変更する予定です。
暗号化
設定ファイルを公開鍵暗号方式で保護することができます。設定ファイルには公開鍵で暗号化した暗号文を記述し、実行環境では秘密鍵を用いて復号して読み取ります。公開鍵はその名の通り公開してしまってもいいため、暗号化された設定ファイルとともにGitHubなどに挙げてしまっても安心です。
秘密にしたい設定値を暗号化して設定ファイルに書くことになるわけですが、このとき、設定したいキーに接尾語.secure
をつけます。例えばdatasource.password
の暗号文はdatasource.password.secure
に設定します。
datasource:
url: postgresql://localhost:5432/sample_app
username: sample_app
password:
secure: <Base64エンコードされた暗号文>
上述のオブジェクトの仕様により、次のように書くこともできます。
datasource:
url: postgresql://localhost:5432/sample_app
username: sample_app
password.secure: <Base64エンコードされた暗号文>
この仕様はTravis CIのEncryption keysを参考にしています。
秘密鍵はBase64エンコードして環境変数NODE_CONFIG_PRIVATE_KEY
に設定します。あとは通常の設定ファイルと同様にnobushi-configをインポートするだけです。
import config from 'nobushi-config';
console.log(config.datasource.password); // => 復号された設定値
config/development.yml
に暗号化されていない開発用データベースのパスワード、config/production.yml
に暗号化された本番用のパスワード、というように使えます。
環境変数
設定値が文字列で${}
で囲まれた文字列がある場合、環境変数を展開します。
server:
port: ${PORT}
datasource:
url: ${HEROKU_POSTGRESQL_CRIMSON_URL}
message: Hello, ${USER}
middlewareとfilter
暗号化と環境変数の動作はmiddlewareとfilterという仕組みにより実現しています。筆者の勤務先では、データベースのパスワードやJWTのsecretなどを安全にテスト環境や本番環境に配信する仕組みがあり、公開鍵暗号方式ではなくこちらを使いたいこともあります(安全に配信できる仕組み自体はどのみち公開鍵暗号ですが)。そのような場合には独自にmiddlewareやfilterを定義することもできます。この件は長くなりそうなので、別の機会に書くことにします。
今後の展開
まずは配列に関する奇妙な仕様を変更したいです。設定値に配列があった場合、配列はフラットな構造にせず、配列の中にオブジェクトがある場合はさらにそのオブジェクトをフラットな構造にする、というようにすればいい感じになるのではと思っています。
また、コードはTypeScriptで書いているので、型ファイルもnpmにアップロードするようにしたいです。
-
JSONだと「"」で囲まないととか余分な「,」をつけてはいけないとか、JSだと「module.exports」とか「export default」とか、とにかく儀式的なルールが多い。JSで設定を書けるようになっていると設定ファイルにロジックを書く人が出てくるのもよくない。 ↩