38
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

process.env.NODE_ENV ってなに?

Last updated at Posted at 2020-01-29

Your paragraph text.jpg

1. NODE_ENV ってなに?

NODE_ENV は Node.js でよく使う環境変数です。

◯ 環境変数 NODE_ENV に指定される値

以下2つが指定されます。

項目 内容
production 本番環境のサーバーの NODE_ENV に設定する
development 開発環境のパソコンの NODE_ENV に設定する
補足

production は開発環境のパソコンにも動作確認のため設定します。上記の図や表では簡単のため省略しました。

◯ production と development の違い

公式ドキュメントの和訳です。

NODE_ENVproduction に設定することは一般的に、以下のことを保証します

  • ログ採取は最小限で必要不可欠なレベルに限定されます
  • より高いレベルのキャッシュが導入され、パフォーマンスの最適化が図られます

◯ サンプルコード

macOS, Ubuntu - bash
#
# 1. 環境変数を設定
#
export NODE_ENV="production"


#
# 2. Node.js 起動
#
node
Node.js
process.env.NODE_ENV
// 'production'
(1) Windows - コマンドプロンプト
# 設定
set NODE_ENV=production

# 表示
echo %NODE_ENV%

# 一覧の表示
set
(2) Windows - PowerShell
# 設定
$env:NODE_ENV = "production"

# 表示
echo $Env:NODE_ENV

# 一覧の表示
Get-ChildItem Env:
(3) macOS, Ubuntu - bash, zsh
# 設定
export NODE_ENV="production"

# 表示
echo ${NODE_ENV}

# 一覧の表示
env

◯ 問題

NODE_ENV に指定される値として一般的でないものは、つぎのうちどれですか?

  1. development
  2. staging
  3. production
解答

2. staging

上記リンクを補足します。なぜ「NODE_ENV に development と production 以外を入れると辛」くなるのでしょうか?

自分が作っているアプリは NODE_ENV を参照します。一方で、例えば Next.js や Nuxt と言ったフレームワークも NODE_ENV を参照します。

フレームワークは NODE_ENV には development または production が代入されていることを前提に動作しています。もし勝手に NODE_ENV に例えば staging を設定してしまった場合、フレームワークが動作しなくなります。

もし staging を定義したい場合は、別途自分のアプリ専用の環境変数、例えば APP_ENV のような環境変数を定義すればよいのかなと思いました(小並感)。

環境 NODE_ENV APP_ENV
開発 development development
ステージング production staging
本番 production production

2. process.env ってなに?

  1. process.env プロパティ
  2. process 変数
  3. globalThis 変数

(1) process.env プロパティ

process.env プロパティは、環境変数を保存しています。

process.env プロパティは、ユーザーの環境を含むオブジェクトを返します。
The process.env property returns an object containing the user environment.

(2) process 変数

process オブジェクトは、現在の Node.js プロセス に関する情報や制御を提供します。
The process object provides information about, and control over, the current Node.js process.

(3) globalThis 変数

globalThis からも環境変数が参照できます。

bash
export NODE_ENV='development'
node
Node.js
process.env.NODE_ENV
// 'production'

globalThis.process.env.NODE_ENV
// 'production'
globalThis

globalThis からグローバルスコープの変数が参照できます。

bash
node
Node.js
// (1) var で宣言した変数はグローバルスコープに登録される
var hello = "hello"
globalThis.hello
// 'hello'

// (2) const で宣言した変数はローカルスコープに登録される
const nihao = "nihao"
globalThis.nihao
// undefined

globalThis のプロパティにはグローバルスコープの this の値が含まれており、通常はグローバルオブジェクトに類似しています。
The globalThis global property contains the global this value, which is usually akin to the global object.

グローバルスコープ

「7. 型定義」で後述する TypeScript で process.env.NODE_ENV に型をつけたいときに頭の片隅にあるとよい知識かなと思いました。

この JavaScript のグローバルスコープと Python のグローバルスコープで意味が異なるので個人的に混乱しました。JavaScript のグローバルスコープは以下のとおりです。

❌ そのモジュールの内側ならどこからでも参照できるスコープ
⭕ すべてのモジュールからどこからでも参照できるスコープ

グローバルスコープ- MDN Web Docs 用語集
プログラミング環境において、グローバルスコープとは、他のすべてのスコープを含み、他のすべてのスコープからアクセス可能なスコープを指します。

ローカルスコープ - MDN Web Docs 用語集
ローカルスコープは 変数 をローカルにする変数の特性です(つまり、変数名は グローバルスコープ ではないスコープ内の 値 にのみ結び付けられます)。

globalThis と global と this
Node.js
var message = "Hello, world!"

globalThis.message
// "Hello, world!"

global.message
// "Hello, world!"

this.message
// "Hello, world!"

昔は globalThis ではなく global を使っていたそうです。

安定性: 3 - レガシー。代わりに globalThis を使用してください。
Stability: 3 - Legacy. Use globalThis instead.


詳細はこちら↓

◯ 問題

NODE_ENV を参照するとエラーになってしまうものは、つぎのうちどれですか?

bash
# 環境変数を前置してコマンドを実行することもできます。
# この書き方は `package.json` の `script` プロパティで見ることがあります。
NODE_ENV="production" node
Node.js
// (1)
env.NODE_ENV

// (2)
process.env.NODE_ENV

// (3)
globalThis.process.env.NODE_ENV
解答

(1) env.NODE_ENV

3. 環境変数はいつ使うの?

開発環境のパソコンと本番環境のサーバーで異なる値を使いたいときに使うのかなと思っています。

  • 環境変数 -「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典

(1) 開発環境と本番環境を識別するとき

詳細
  • 開発環境のコードを本番環境にデプロイするとデバッグログがブラウザに表示されてセキュリティがよろしくない状態になることがあります。デプロイの切り替えを手動で行うとミスしてしまう可能性があります。このような事態を避けるために開発環境には development, 本番環境には production を指定して自動で環境を区別するとよいような気がします
  • Python のフレームワークである Flask の事例ですが、開発環境のコードをデプロイしてしまい、デバッグ用の SQL を生でブラウザから実行できる画面が表示された状態になり大変なことになったという話を YouTube で聞いたことがあります。リンク先の YouTube の動画で Flask の作者 Armin Ronacher が 14:49 に incident 事件について語ってくれています
  • Armin Ronacher, "Flask for Fun and Profit", PyBay2016

(2) パスワードを保存するとき

詳細
  • パスワードをソースコードに直接書き込み、GitHub に公開してしまい大変なことになったという話もたまに聞きます。
  • ソースコードを公開しなければパスワードをベタ書きしてもいいんじゃない?って思ったのですが、公開、非公開の有無に関わらずパスワードは環境変数に書きましょうね、というのが一般的なお作法のようです。
  • 考えてみるとソースコードを GitHub で公開しなかったとしてもソースコードにパスワードを書いてしまった場合、git clone するごとにパスワードがいろいろなところに拡散してしまうことになります。環境変数を使い一箇所だけでパスワードを保存しておくほうが安全な気がします(小並感)。

4. 環境変数とシェル変数

環境変数とシェル変数の違い
Windows - コマンドプロンプト なし *1
Windows - PowerShell あり
macOS, Ubuntu - bash, zsh あり

*1 Windows - コマンドプロンプトではすべて環境変数として扱われます。

例えば export コマンドは環境変数を設定するためのものです。export をつけ忘れると シェル変数 だけしか変更されず、環境変数 は変更されません。

bash
#
# 1. export をつける
#
export NODE_ENV='development'
echo ${NODE_ENV}
# 'development'

#
# 2. export をつけない
#
APP_ENV='production'  # <--- export をつけなくても
echo ${APP_ENV}
# 'production'          <--- bash では表示されるが...

node
Node.js
process.env.NODE_ENV
// 'development'

process.env.APP_ENV
// ???

◯ 問題

上記 ??? には何が表示されますか?

  1. production
  2. undefined
答え

2. undefined

5. 環境変数とファイル

環境変数に書き込むのとファイルに書き込むことの違いってなんなのでしょうか?なんで環境編巣が存在するのでしょうか?ファイルから値を読み込むのは面倒だから、簡単に環境変数からよく使う値だけを値を読み込めるようにしましたみたいな感じなんでしょうか?🤔

個人的な備忘録です。

そもそも環境変数とは?

簡単に言うと環境変数とは、プログラムの挙動を調整するためのパラメータの一種 ... 中略 ... OSとしてプログラム実行時に指定するパラメータは ... 中略 ... 2種類しかありません。その1つがコマンドライン引数、もう一つが環境変数です。

(1) コマンドから呼び出すとき

環境変数に書き込むとコマンドの引数に与えるのが簡単

bash
# いいサンプルが思い浮かばない... orz
export DB_PASS=password
echo ${DB_PASS}

ファイルに書き込むとコマンドの引数に与えるのが面倒(頑張ればできる)

bash
echo "password" > DB_PASS.txt
echo $(cat DB_PASS.txt)

(2) Node.js から呼び出すとき

環境変数に書き込むと読みこむのが簡単

bash
export DB_PASS=password
node
Node.js
console.log(process.env.DB_PASS)

ファイルに書き込むと読みこむのが面倒(頑張ればできる)

bash
echo '{ "DB_PASS": "password" }' > ./env.json
node
Node.js
const fs = require('fs')
const env = JSON.parse(fs.readFileSync('./env.json', 'utf8'))
console.log(env.DB_PASS) 

6. 型定義

あんまりよくわかっていません...

TypeScript をお使いの場合、プロジェクトルート配下に env.d.ts を定義すると process.env に補完が効いて便利です。

env.d.ts
// 1. プロジェクトのルートディレクトリに
//    env.d.ts ファイルを作成
// 2. コピペで貼り付け
declare module 'process' {
	global {
		namespace NodeJS {
			interface ProcessEnv {
				readonly NODE_ENV?: "development" | "production"
                readonly BACKEND_URL?: string
                readonly DATABASE_URL?: string
           }
	    }
    }
}

◯ なんで動くの?

上記の env.d.ts の定義で下記の process.d.ts の定義を 上書き しているから

process.d.ts
declare module "process" {
    
    // 中略

    global {
    
        // NodeJS.Process は
        // NodeJS という namespace の中の Process という型
        var process: NodeJS.Process;

        namespace NodeJS {
            
            // 中略

            // env.d.ts は
            // ここの部分を上書きして定義をしている
            interface ProcessEnv extends Dict<string> {
                /**
                 * Can be used to change the default timezone at runtime
                 */
                TZ?: string;
            }

            // 中略

            // NodeJS.Process
            interface Process extends EventEmitter { 

                // 中略

                // env プロパティ
                env: ProcessEnv;

                // 中略

            }
        }
    }
    
    export = process;
}

以下のページで教わりました。誠にありがとうございます。

(1) interface

interface を使えば型を 上書き できます。type ではできません。

interface Person {
  name: string
}

interface Person {
  email: string
}

// name と email 両方必要になる
const john: Person = {
  name: "john",
  email: "hoge@example.com"
}

(2) declare module

process を (a) と (b) の 2箇所で定義 しています。

process.d.ts
// (a) モジュールの `process` の型を定義をしている
declare module "process" {

    // (b) グローバルスコープの `process` の型を定義している
    global {
        var process: NodeJS.Process;
    }

    // (b) で定義した process を (a) に export している
    export = process;
}

declare moduleimport したり require したりして得られたモジュールに型をつけるためのものだと思っています。

なんでグローバルスコープの process だけでなくモジュールの process の型を定義しないといけないの?🤔という話なのですが process は以下のように明示的に require できるモジュールだからだと思っています。

const nodeProcess = require('node:process')
nodeProcess === process
// true

(3) declare global (global)

declare global はグローバルスコープにあるオブジェクトに型をつけられるものだと思っています。


なんで declare global じゃなくて global なんだろう?🤔と思ったら二重でつけるのは禁止されているケースもあるそうです。

いくつかのケースでは、二重にdeclareをつけること自体が禁止されています。

export declare namespace foo {
    // 自動的にアンビエントになる (declareを二重につけることはできない)
    export const x: number;
}

ものによっては declare global がついていないコードも見かけますが...

interface Window {
  dataLayer: object[];
}

window.dataLayer.push({ event: "event_name" });

あまりよろしくなさそうです...

window にメンバを増やすために以下のように書いている事例がたまにあります。

interface Window {
  myGlobalVariable: number;
}

まず大前提として、これはスクリプトファイルか declare global 内でしか動作しません。 (さもなくば単に新しい Window というインターフェースが定義されるだけ)

(4) export = process;

export = process;module.exports = process; に近い機能のようです。

通常の export default 構文と異なり、export = は CommonJS スタイルの module.exports のように、モジュール全体をその変数 process として直接エクスポートすることを示しています。

  • 上記は ChatGPT 調べ

(5) namespace NodeJS

なんでわざわざ NodeJS という namespace を設けたんだろう?🤔と思ったのですが、global スコープを整理するためなんだろうなと思っています。

(6) env.d.ts ファイル

.d.ts 拡張子のついたファイルを読み込んでくれるそうです。

コンパイラは適切なパスに従って、.ts、.tsxを見つけようとし、次に.d.tsを探します。 もし特定のファイルが見つからなかった場合、コンパイラはambientモジュール宣言を探し、 .d.tsファイル内にある必要な宣言を呼び戻します。
名前空間(namespace)とモジュール - js STUDIO

◯ そのほか

まだ正直よくわかっていない...

8. おわりに

↓ NODE_ENV の取り扱いの話をされています。

38
22
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?