CLOVER🍀

That was when it all began.

Node.jsで環境変数をファイルから読み込むdotenvを試す

これは、なにをしたくて書いたもの?

Node.jsで環境変数をファイルから読み込むことができるdotenvというものがあるらしいので、試してみることにしました。

dotenv

dotenvのGitHubリポジトリーは、こちら。

GitHub - joho/godotenv: A Go port of Ruby's dotenv library (Loads environment variables from .env files)

dotenvをベースにしているdotenv-vaultというプロダクトのWebサイトを見ると、

Simplify Your Secrets | Dotenv

dotenvはNode.js以外にも存在しているようです。

今回はNode.js向けのdotenvのみにフォーカスします。

使い方としては、.envというファイルを作成して

.env

S3_BUCKET="YOURS3BUCKET"
SECRET_KEY="YOURSECRETKEYGOESHERE"

dotenvモジュールをインポートすればよいみたいです。

require('dotenv').config()
console.log(process.env) // remove this after you've confirmed it is working

あとはprocess.env.[環境変数名]で環境変数が参照できるようになります。

複数行の記述もできるようです。

PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
...
Kh9NV...
...
-----END RSA PRIVATE KEY-----"

--require(または-r)オプションで、プリロードすることもできるようです。

$ node -r dotenv/config your_script.js

オプションをコマンドライン引数で指定したり

$ node -r dotenv/config your_script.js dotenv_config_path=/custom/path/to/.env dotenv_config_debug=true

環境変数で指定することもできるようです。

$ DOTENV_CONFIG_<OPTION>=value node -r dotenv/config your_script.js
$ DOTENV_CONFIG_ENCODING=latin1 DOTENV_CONFIG_DEBUG=true node -r dotenv/config your_script.js dotenv_config_path=/custom/path/to/.env

とりあえず、試していってみることにしましょう。

環境

今回の環境は、こちら。

$ node --version
v18.16.1


$ npm --version
9.5.1

dotenvを使ってみる

では、dotenvを使ってみましょう。

Node.jsプロジェクトを作成。

$ npm init -y
$ npm i -D prettier

Prettierも入れています。

  "devDependencies": {
    "prettier": "^2.8.8"
  },

.prettierrc.json

{
  "singleQuote": true,
  "printWidth": 120
}

とりあえず、環境変数を標準出力に書き出すJavaScriptを書いてみます。

print-env.js

console.log(`get env: MY_ENV_VAR = ${process.env.MY_ENV_VAR}`);

実行。

$ node print-env.js
get env: MY_ENV_VAR = undefined

この状態だと環境変数を設定していないので当然undefinedです。

環境変数を設定すれば当然ながら参照します。

$ MY_ENV_VAR=Hello node print-env.js
get env: MY_ENV_VAR = Hello

これでもいいのですが、環境変数が複数あったり使用する値を切り替えたかったりすると煩雑になってきますね。
こういう時にdotenvを使うとよいのだろうと思います。

というわけで、dotenvをインストール。

$ npm i dotenv

インストールされたdotenvのバージョン。

  "dependencies": {
    "dotenv": "^16.3.1"
  }

使い方としては、まずは.envというファイルを用意するようです。

dotenv / Usage

.env

MY_ENV_VAR=Hello from dotenv

次に、先ほどのJavaScriptファイルにdotenvのロードを追加します。

print-env.js

require('dotenv').config();

console.log(`get env: MY_ENV_VAR = ${process.env.MY_ENV_VAR}`);

これで実行すると、.envファイルに設定した内容を環境変数に追加してくれます。

$ node print-env.js
get env: MY_ENV_VAR = Hello from dotenv

明示的な追加が必要なので、dotenvをロードしている部分をコメントアウトすると

print-env.js

// require('dotenv').config();

console.log(`get env: MY_ENV_VAR = ${process.env.MY_ENV_VAR}`);

.envファイルを読み込まなくなります。

$ node print-env.js
get env: MY_ENV_VAR = undefined

個人的にはこの「ソースコードにdotenvの使用を明示的に追加する」という方法はちょっと嫌なので、
Node.jsの--require(-r)オプションを使ったプリロードの方が良いかなと思います。
コンテナ環境とかだと、環境変数はコンテナ側の設定で行うことが多いと思いますし。

dotenv / Preload

こうすると、ソースコードにdotenvのロードを書かなくとも動作させることができます。

$ node -r dotenv/config print-env.js
get env: MY_ENV_VAR = Hello from dotenv

ちなみに、.envファイルに書かれている環境変数がすでに存在した場合は、あらかじめ環境変数として定義されている値が優先される
ようです。

$ MY_ENV_VAR=Hello node -r dotenv/config print-env.js
get env: MY_ENV_VAR = Hello

読み込むファイルを切り替える

dotenvはデフォルトで.envというファイルを読み込みますが、これを別のファイルに切り替えることができます。

こんなファイルを用意。

.env-other

MY_ENV_VAR=Hello from dotenv other

dotenvの設定は、コマンドラインオプションでdotenv_config_<option>=valueと指定するか、
環境変数でDOTENV_CONFIG_<OPTION>=valueで指定することができます。

今回は環境変数を使いましょう。読み込むファイルを.env以外にするにはDOTENV_CONFIG_PATH環境変数を指定します。

確認。

$ DOTENV_CONFIG_PATH=.env-other  node -r dotenv/config print-env.js
get env: MY_ENV_VAR = Hello from dotenv other

読み込むファイルが切り替わりました。

コメントや複数行の環境変数を書く

dotenvで読み込むファイルには、コメントを書いたり、環境変数を複数行で定義できたりします。

複数行の環境変数を定義するには、"または'で囲って複数行で書きます。コメントは#で書けばOKです。

こんな感じですね。

.env-multiple

# これは、シンプルな環境変数定義です
MY_ENV_VAR=Hello World!!

# これは、複数行の環境変数定義です
MY_ENV_MULTILINE_VAR="begin

\"クォートは\でエスケープして記述\"

end"

読み込むソースコード。

print-env-multiple.js

console.log(`get env: MY_ENV_VAR = ${process.env.MY_ENV_VAR}`);

console.log();

console.log(`get env: MY_ENV_MULTILINE_VAR = ${process.env.MY_ENV_MULTILINE_VAR}`);

確認。

$ DOTENV_CONFIG_PATH=.env-multiple  node -r dotenv/config print-env-multiple.js
get env: MY_ENV_VAR = Hello World!!

get env: MY_ENV_MULTILINE_VAR = begin

\"クォートは\でエスケープして記述\"

end

OKですね。

Jestと組み合わせる

最後は、dotenvをJestを組み合わせて使ってみましょう。

新しくNode.jsプロジェクトを作成。

$ npm init -y
$ npm i -D prettier
$ npm i dotenv
$ npm i -D jest

依存関係は、こんな感じになりました。

  "devDependencies": {
    "jest": "^29.5.0",
    "prettier": "^2.8.8"
  },
  "dependencies": {
    "dotenv": "^16.3.1"
  }

scriptsはこうしておきます。

  "scripts": {
    "format": "prettier  --write **/*.js",
    "test": "jest"
  },

.envファイルを用意。

.env

MY_ENV_VAR=Hello dotenv

テストコード。

use-dotenv.test.js

test('use dotenv', () => {
  expect(process.env.MY_ENV_VAR).toBe('Hello dotenv');
});

このテストはこのまま実行しても失敗します。

$ npm run test

> dot-env-jest-example@1.0.0 test
> jest

 FAIL  ./use-dotenv.test.js
  ✕ use dotenv (4 ms)

  ● use dotenv

    expect(received).toBe(expected) // Object.is equality

    Expected: "Hello dotenv"
    Received: undefined

      1 | test('use dotenv', () => {
    > 2 |   expect(process.env.MY_ENV_VAR).toBe('Hello dotenv');
        |                                  ^
      3 | });
      4 |

      at Object.toBe (use-dotenv.test.js:2:34)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        0.288 s, estimated 1 s
Ran all test suites.

こうすれば動作しますが、それもちょっと違う気がします…。

use-dotenv.test.js

require('dotenv').config();

test('use dotenv', () => {
  expect(process.env.MY_ENV_VAR).toBe('Hello dotenv');
});

どうしたらいいかですが、Jestの設定ファイルでsetupFilesというものを使えば良さそうです。

Configuring Jest / setupFiles

dotenvを使う分にはでもよさそうでしたが。

Configuring Jest / setupFilesAfterEnv

設定。

jest.config.js

module.exports = {
  setupFiles: ['dotenv/config'],
  testEnvironment: 'node',
};

これでdotenvが動作するようになります。

$ npm run test

> dot-env-jest-example@1.0.0 test
> jest

 PASS  ./use-dotenv.test.js
  ✓ use dotenv (3 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.236 s, estimated 1 s
Ran all test suites.

別のファイルを読み込ませたい場合は

.env-other

MY_ENV_VAR=Hello dotenv other

環境変数の指定をscriptsに含めればよいのかな、と。

  "scripts": {
    "format": "prettier  --write **/*.js",
    "test": "jest",
    "test-other": "DOTENV_CONFIG_PATH=.env-other jest"
  },

確認。

$ npm run test-other

> dot-env-jest-example@1.0.0 test-other
> DOTENV_CONFIG_PATH=.env-other jest

 FAIL  ./use-dotenv.test.js
  ✕ use dotenv (4 ms)

  ● use dotenv

    expect(received).toBe(expected) // Object.is equality

    Expected: "Hello dotenv"
    Received: "Hello dotenv other"

      2 |
      3 | test('use dotenv', () => {
    > 4 |   expect(process.env.MY_ENV_VAR).toBe('Hello dotenv');
        |                                  ^
      5 | });
      6 |

      at Object.toBe (use-dotenv.test.js:4:34)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        0.251 s, estimated 1 s
Ran all test suites.

こんなところでしょうか。

オマケ

最後に少しdotenvのソースコードを眺めておきます。

小さなライブラリーなので、中身を見るのもそう苦労しないでしょう。

オプションを環境変数およびコマンドラインオプションから解決している部分。

https://github.com/motdotla/dotenv/blob/v16.3.1/config.js

https://github.com/motdotla/dotenv/blob/v16.3.1/lib/env-options.js

https://github.com/motdotla/dotenv/blob/v16.3.1/lib/cli-options.js

.envファイルのパース。

https://github.com/motdotla/dotenv/blob/v16.3.1/lib/main.js#L12-L48

まとめ

Node.jsで環境変数をファイルから読み込むdotenvを試してみました。

なんとなくこういうものがあるというのは知っていましたが、実際に使ったことはなかったので確認しておいてよかったです。
他の言語にも同じようなものがあることもわかりましたし。