1. NODE_ENV ってなに?
NODE_ENV は Node.js でよく使う環境変数です。
◯ 環境変数 NODE_ENV に指定される値
以下2つが指定されます。
項目 | 内容 |
---|---|
production | 本番環境のサーバーの NODE_ENV に設定する |
development | 開発環境のパソコンの NODE_ENV に設定する |
補足
production
は開発環境のパソコンにも動作確認のため設定します。上記の図や表では簡単のため省略しました。
◯ production と development の違い
公式ドキュメントの和訳です。
NODE_ENV
をproduction
に設定することは一般的に、以下のことを保証します
- ログ採取は最小限で必要不可欠なレベルに限定されます
- より高いレベルのキャッシュが導入され、パフォーマンスの最適化が図られます
◯ サンプルコード
#
# 1. 環境変数を設定
#
export NODE_ENV="production"
#
# 2. Node.js 起動
#
node
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
に指定される値として一般的でないものは、つぎのうちどれですか?
- development
- staging
- production
解答
2. staging
- ステージング環境 -「分かりそう」で「分からない」でも「分かった」気になれる...
- NODE_ENV に development と production 以外を入れると辛い
上記リンクを補足します。なぜ「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 |
NODE_ENV=productionが最適化のために使われてるので、NODE_ENV=stagingのような使い方はしないで別の名前の環境変数を使おうという話。 "NODE ENV=production is a lie - YouTube" https://t.co/7uCLayNlTy #nodejs #video
— azu (@azu_re) November 17, 2023
2. process.env ってなに?
-
process.env
プロパティ -
process
変数 -
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
からも環境変数が参照できます。
export NODE_ENV='development'
node
process.env.NODE_ENV
// 'production'
globalThis.process.env.NODE_ENV
// 'production'
globalThis
globalThis
からグローバルスコープの変数が参照できます。
node
// (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
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
を参照するとエラーになってしまうものは、つぎのうちどれですか?
# 環境変数を前置してコマンドを実行することもできます。
# この書き方は `package.json` の `script` プロパティで見ることがあります。
NODE_ENV="production" node
// (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 するごとにパスワードがいろいろなところに拡散してしまうことになります。環境変数を使い一箇所だけでパスワードを保存しておくほうが安全な気がします(小並感)。
GitHubで「OPENAI_API_KEY」などのワードで検索すると、多くの開発者が秘密鍵を公開リポジトリに誤ってアップロードしている例が見つかります。これは企業に対する教訓となります。たとえ開発者のような技術者であっても、信用してはいけません。GitHubとOpenAIは実際に、公開GitHubリポジトリにアップ… pic.twitter.com/KPelm617eb
— Jeffry Alvarado (@jalva_dev) June 16, 2024
4. 環境変数とシェル変数
環境変数とシェル変数の違い | |
---|---|
Windows - コマンドプロンプト | なし *1 |
Windows - PowerShell | あり |
macOS, Ubuntu - bash, zsh | あり |
*1 Windows - コマンドプロンプトではすべて環境変数として扱われます。
例えば export
コマンドは環境変数を設定するためのものです。export
をつけ忘れると シェル変数 だけしか変更されず、環境変数 は変更されません。
#
# 1. export をつける
#
export NODE_ENV='development'
echo ${NODE_ENV}
# 'development'
#
# 2. export をつけない
#
APP_ENV='production' # <--- export をつけなくても
echo ${APP_ENV}
# 'production' <--- bash では表示されるが...
node
process.env.NODE_ENV
// 'development'
process.env.APP_ENV
// ???
◯ 問題
上記 ???
には何が表示されますか?
production
undefined
5. 環境変数とファイル
環境変数に書き込むのとファイルに書き込むことの違いってなんなのでしょうか?なんで環境編巣が存在するのでしょうか?ファイルから値を読み込むのは面倒だから、簡単に環境変数からよく使う値だけを値を読み込めるようにしましたみたいな感じなんでしょうか?🤔
個人的な備忘録です。
そもそも環境変数とは?
簡単に言うと環境変数とは、プログラムの挙動を調整するためのパラメータの一種 ... 中略 ... OSとしてプログラム実行時に指定するパラメータは ... 中略 ... 2種類しかありません。その1つがコマンドライン引数、もう一つが環境変数です。
(1) コマンドから呼び出すとき
環境変数に書き込むとコマンドの引数に与えるのが簡単
# いいサンプルが思い浮かばない... orz
export DB_PASS=password
echo ${DB_PASS}
ファイルに書き込むとコマンドの引数に与えるのが面倒(頑張ればできる)
echo "password" > DB_PASS.txt
echo $(cat DB_PASS.txt)
(2) Node.js から呼び出すとき
環境変数に書き込むと読みこむのが簡単
export DB_PASS=password
node
console.log(process.env.DB_PASS)
ファイルに書き込むと読みこむのが面倒(頑張ればできる)
echo '{ "DB_PASS": "password" }' > ./env.json
node
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
に補完が効いて便利です。
// 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
の定義を 上書き しているから
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"
}
- TypeScript で type と interface どっち使うのか問題 - Zenn
- You Should Be Using Types Instead Of Interfaces In TypeScript - YouTube
(2) declare module
process
を (a) と (b) の 2箇所で定義 しています。
// (a) モジュールの `process` の型を定義をしている
declare module "process" {
// (b) グローバルスコープの `process` の型を定義している
global {
var process: NodeJS.Process;
}
// (b) で定義した process を (a) に export している
export = process;
}
declare module
は import
したり require
したりして得られたモジュールに型をつけるためのものだと思っています。
なんでグローバルスコープの process
だけでなくモジュールの process
の型を定義しないといけないの?🤔という話なのですが process
は以下のように明示的に require
できるモジュールだからだと思っています。
const nodeProcess = require('node:process')
nodeProcess === process
// true
(3) declare global (global)
declare global
はグローバルスコープにあるオブジェクトに型をつけられるものだと思っています。
Remember folks, declaring globals isn't that hard. pic.twitter.com/st1ZJfFRMk
— Matt Pocock (@mattpocockuk) January 10, 2024
なんで 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 の取り扱いの話をされています。