Node.jsでコマンドライン引数を受け取るには process.argv(文字列の配列)を使い、オプションつきの引数は標準の util.parseArgs で解析します。環境変数は process.env から読み取り、値はすべて文字列で届くので、数値や真偽値は自分で変換します。

この記事では、process.argv の中身、util.parseArgs のオプション定義、process.env の変換と既定値、.env ファイルの読み込み(--env-file と process.loadEnvFile)、npm run 経由で引数を渡すときの「--」までを、Node.js 22.22.2 / npm 10.9.7 で実際に動かした出力つきでまとめます。追加のパッケージは使わず、Node.js の標準機能だけで完結します。

なお、出力に含まれるパスやユーザー名は一般的な名前(/home/taro など)に置き換えています。表示は環境に合わせて置き換えています。

コマンドライン引数と環境変数の早見表

やりたいこと 書き方 補足
引数をそのまま配列で受け取る process.argv.slice(2) 先頭2つは node 本体とスクリプトのパス
1つ目の引数と残りを分ける const [, , command, ...rest] = process.argv 引数がなければ command は undefined
--name taro や -v を解析する parseArgs({ options, allowPositionals: true }) node:util から import
値つきオプションを定義する { type: 'string', short: 'n' } 値は必ず文字列で届く
フラグ(あり/なし)を定義する { type: 'boolean', short: 'v' } 指定されると true
既定値を持たせる { type: 'string', default: '3000' } 未指定のときに使われる
同じオプションを何度も受け取る { type: 'string', multiple: true } 値が配列になる
環境変数を読む process.env.PORT 未設定なら undefined
環境変数を数値にする Number(process.env.PORT) 整数かどうかの確認とセットで
環境変数を真偽値にする process.env.DEBUG === 'true' 文字列 "false" は真と判定される点に注意
未設定のときの既定値 process.env.PORT ?? '3000' 空文字も既定値にしたいなら自作関数で
.env を読み込んで起動する node --env-file=.env app.mjs シェルで設定済みの値が優先
コードの中で .env を読む process.loadEnvFile() 既定はカレントディレクトリの .env
npm run 経由で引数を渡す npm run cli -- -n taro 「--」より後ろがスクリプトに届く

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Node.jsコマンドライン引数と環境変数|process.argv・env」

process.argv の中身を確かめる

先頭2つは node とスクリプトのパス

process.argv は、起動時のコマンドラインを空白で区切った文字列の配列です。 まず中身をそのまま表示してみます。

// argv.mjs
console.log(process.argv);
console.log('引数だけ:', process.argv.slice(2));
$ node argv.mjs one --two=2 -x "a b"
[
  '/usr/local/bin/node',
  '/home/taro/argv-demo/argv.mjs',
  'one',
  '--two=2',
  '-x',
  'a b'
]
引数だけ: [ 'one', '--two=2', '-x', 'a b' ]
位置 中身 例
process.argv[0] node 本体の絶対パス(process.execPath と同じ) /usr/local/bin/node
process.argv[1] 実行したスクリプトの絶対パス /home/taro/argv-demo/argv.mjs
process.argv[2] 以降 利用者が渡した引数 one、--two=2 …

読み取れるポイントは3つです。

  • 引数がほしいときは slice(2) で先頭2つを外します。
  • すべて文字列です。 --two=2 の 2 も数値にはなりません。
  • process.argv は「--」や「=」の意味を解釈しません。 --two=2 は1つの文字列のままです。解釈は自分で書くか、後述の parseArgs に任せます。

引用符とワイルドカードはシェルが先に処理する

空白を含む値は引用符で囲むと1つの引数になります。また、bash では *.json のようなワイルドカードは Node.js に届く前にファイル名へ展開されます。

$ node argv.mjs "a b" *.json
[
  '/usr/local/bin/node',
  '/home/taro/argv-demo/argv.mjs',
  'a b',
  'package.json'
]
引数だけ: [ 'a b', 'package.json' ]

引用符そのものは配列に入らず、*.json は package.json に置き換わっています。 ワイルドカードを文字のまま渡したいときは '*.json' と引用符で囲みます。

node 自身のオプションは process.execArgv に入る

スクリプト名より前に書いた Node.js 向けのオプションは、process.argv には入りません。

$ node --no-warnings argv.mjs x
[
  '/usr/local/bin/node',
  '/home/taro/argv-demo/argv.mjs',
  'x'
]
引数だけ: [ 'x' ]

$ node --env-file=.env -p "process.execArgv"
[ '--env-file=.env', '-p', 'process.execArgv' ]
書く位置 届く先
node と スクリプト名の間 process.execArgv(Node.js 自身の設定)
スクリプト名より後ろ process.argv(スクリプトへの引数)

「オプションが効かない」と感じたら、スクリプト名の前後どちらに書いたかを確かめると早く解決します。

引数が少ないなら分割代入で十分

サブコマンドと残りを受け取るだけなら、分割代入が読みやすい書き方です。

// simple.mjs
const [, , command, ...rest] = process.argv;
console.log({ command, rest });
$ node simple.mjs build src dist
{ command: 'build', rest: [ 'src', 'dist' ] }

$ node simple.mjs
{ command: undefined, rest: [] }

引数を省略すると undefined になるので、必須の引数は if (!command) で確かめて使い方を表示すると親切です。

util.parseArgs でオプションを解析する

--name taro、-v、--port=8080 のようなオプションを process.argv から自前で読み取ろうとすると、「=」の有無、短い名前、まとめ書き(-vh)、打ち間違いの検出と、書くことが増えていきます。Node.js には標準で util.parseArgs があり、これらをまとめて引き受けてくれます。 公式ドキュメント(util の章)では v18.3.0 / v16.17.0 で追加と記載されています。本記事の結果はすべて Node.js 22.22.2 のものです。

オプション定義の一覧表

parseArgs には設定オブジェクトを1つ渡します。

設定 型 既定値 意味
args 文字列の配列 process.argv.slice(2) 相当 解析する引数。テストで配列を直接渡すときに使う
options オブジェクト なし 受け付けるオプションの定義(下の表)
strict boolean true 未定義のオプションや型の不一致をエラーにする
allowPositionals boolean strict が true のとき false オプション以外の引数(ファイル名など)を受け付ける
allowNegative boolean false --no-color の形で boolean を false にできる
tokens boolean false 解析の途中経過(どの位置に何があったか)も返す

options の中は「オプション名: 定義」の形です。

定義のキー 値 意味 例
type 'string' または 'boolean' 必須。値を取るか、フラグか { type: 'string' }
short 1文字 短い名前 short: 'n' で -n
default type に合う値 未指定のときの値 default: '3000'
multiple boolean 何度も指定でき、値が配列になる -t a -t b → ['a', 'b']

戻り値は { values, positionals } です。values がオプション、positionals がそれ以外の引数です。

type に 'number' はありません。 数値がほしいときは 'string' で受けて、自分で Number に変換します(雛形に入れてあります)。

コピペ用の雛形(cli.mjs)

string / boolean / short / default / multiple / エラー処理 / ヘルプ / 数値変換をひととおり入れた雛形です。 そのまま保存して動かせます。

// cli.mjs  (Node.js 22 で確認。追加パッケージなし)
import { parseArgs } from 'node:util';

const options = {
  name:    { type: 'string',  short: 'n' },
  port:    { type: 'string',  short: 'p', default: '3000' },
  verbose: { type: 'boolean', short: 'v', default: false },
  tag:     { type: 'string',  short: 't', multiple: true, default: [] },
  help:    { type: 'boolean', short: 'h' },
};

let parsed;
try {
  parsed = parseArgs({ options, allowPositionals: true });
} catch (err) {
  console.error(`引数エラー: ${err.message} (${err.code})`);
  console.error('使い方: node cli.mjs [-n 名前] [-p ポート] [-v] [-t タグ]... ファイル...');
  process.exit(2);
}

const { values, positionals } = parsed;
if (values.help) {
  console.log('使い方: node cli.mjs [-n 名前] [-p ポート] [-v] [-t タグ]... ファイル...');
  process.exit(0);
}

const port = Number(values.port);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
  console.error(`--port は 1〜65535 の整数で指定してください: ${values.port}`);
  process.exit(2);
}

console.log({ ...values, port, positionals });

何も渡さない場合と、ひととおり渡した場合の結果です。

$ node cli.mjs
{ port: 3000, verbose: false, tag: [], positionals: [] }

$ node cli.mjs -n taro --port 8080 -v -t a -t b in.txt out.txt
{
  name: 'taro',
  port: 8080,
  verbose: true,
  tag: [ 'a', 'b' ],
  positionals: [ 'in.txt', 'out.txt' ]
}

default を書かなかった name は、未指定のときキーごと存在しません(undefined が入るのではなく、values に name が現れません)。必須にしたいときは if (!values.name) で確認します。

CommonJS でも同じ関数が使えます。values はプロトタイプを持たないオブジェクトとして返ります。

// cjs.cjs
const { parseArgs } = require('node:util');
const { values } = parseArgs({ options: { name: { type: 'string' } } });
console.log(values);
$ node cjs.cjs --name taro
[Object: null prototype] { name: 'taro' }

受け付ける書き方のバリエーション

同じ雛形で、次の書き方がすべて通ります。

$ node cli.mjs --name=taro -p8080 -vh
使い方: node cli.mjs [-n 名前] [-p ポート] [-v] [-t タグ]... ファイル...

$ node cli.mjs -n a -n b
{ name: 'b', port: 3000, verbose: false, tag: [], positionals: [] }

$ node cli.mjs -- --name x
{ port: 3000, verbose: false, tag: [], positionals: [ '--name', 'x' ] }
書き方 解釈
--name taro 長い名前と値を空白で区切る
--name=taro 長い名前と値を「=」でつなぐ
-n taro 短い名前と値
-p8080 短い名前に値を続けて書く
-vh 短い boolean をまとめて書く(-v と -h)
-n a -n b multiple でなければ後に書いたほうが使われる
-- ここから後ろはすべて positionals として扱う

エラーになる例とエラーコード

strict が true(既定)のとき、定義と合わない引数は例外になります。雛形の catch で受けた実際のメッセージです。

$ node cli.mjs --prot 8080
引数エラー: Unknown option '--prot'. To specify a positional argument starting with a '-', place it at the end of the command after '--', as in '-- "--prot" (ERR_PARSE_ARGS_UNKNOWN_OPTION)
使い方: node cli.mjs [-n 名前] [-p ポート] [-v] [-t タグ]... ファイル...

$ node cli.mjs --name
引数エラー: Option '-n, --name <value>' argument missing (ERR_PARSE_ARGS_INVALID_OPTION_VALUE)
使い方: node cli.mjs [-n 名前] [-p ポート] [-v] [-t タグ]... ファイル...

$ node cli.mjs --verbose=yes
引数エラー: Option '-v, --verbose' does not take an argument (ERR_PARSE_ARGS_INVALID_OPTION_VALUE)
使い方: node cli.mjs [-n 名前] [-p ポート] [-v] [-t タグ]... ファイル...

$ node cli.mjs -n -v
引数エラー: Option '-n' argument is ambiguous.
Did you forget to specify the option argument for '-n'?
To specify an option argument starting with a dash use '--name=-XYZ' or '-n-XYZ'. (ERR_PARSE_ARGS_INVALID_OPTION_VALUE)
使い方: node cli.mjs [-n 名前] [-p ポート] [-v] [-t タグ]... ファイル...

$ node cli.mjs --port abc
--port は 1〜65535 の整数で指定してください: abc
状況 err.code 対応
定義にないオプション(打ち間違いなど) ERR_PARSE_ARGS_UNKNOWN_OPTION options に追加する、またはつづりを直す
string なのに値がない ERR_PARSE_ARGS_INVALID_OPTION_VALUE 値を付ける
boolean に値を付けた ERR_PARSE_ARGS_INVALID_OPTION_VALUE 値を外す
値がハイフンで始まる(-5 など) ERR_PARSE_ARGS_INVALID_OPTION_VALUE --num=-5 と「=」でつなぐ
allowPositionals なしでファイル名などを渡した ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL allowPositionals: true を付ける

打ち間違いをその場で知らせてくれるのが strict の良さです。 --prot が黙って無視され、既定の 3000 番で起動してしまう、といった行き違いを防げます。最後の --port abc は parseArgs では通り、雛形の数値チェックで止まっています。

strict・allowNegative・tokens・multiple の挙動

設定ごとの違いを、args に配列を直接渡して確かめました(テストを書くときもこの形が便利です)。

// t.mjs
import { parseArgs } from 'node:util';
const show = (label, cfg) => {
  try { console.log(label, JSON.stringify(parseArgs(cfg))); }
  catch (e) { console.log(label, e.code, e.message); }
};
show('positional禁止', { args: ['file.txt'], options: {} });
show('strict:false', { args: ['--foo', '--bar=1', 'x', '-z'], strict: false });
show('allowNegative', { args: ['--no-color'], options: { color: { type: 'boolean', default: true } }, allowNegative: true });
show('allowNegativeなし', { args: ['--no-color'], options: { color: { type: 'boolean', default: true } } });
show('multiple boolean', { args: ['-v', '-v', '-v'], options: { verbose: { type: 'boolean', short: 'v', multiple: true } } });
show('負の数', { args: ['--num', '-5'], options: { num: { type: 'string' } } });
show('負の数=', { args: ['--num=-5'], options: { num: { type: 'string' } } });
show('未指定', { args: [], options: { a: { type: 'string' }, b: { type: 'boolean' } } });
$ node t.mjs
positional禁止 ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL Unexpected argument 'file.txt'. This command does not take positional arguments
strict:false {"values":{"foo":true,"bar":"1","z":true},"positionals":["x"]}
allowNegative {"values":{"color":false},"positionals":[]}
allowNegativeなし ERR_PARSE_ARGS_UNKNOWN_OPTION Unknown option '--no-color'
multiple boolean {"values":{"verbose":[true,true,true]},"positionals":[]}
負の数 ERR_PARSE_ARGS_INVALID_OPTION_VALUE Option '--num' argument is ambiguous.
Did you forget to specify the option argument for '--num'?
To specify an option argument starting with a dash use '--num=-XYZ'.
負の数= {"values":{"num":"-5"},"positionals":[]}
未指定 {"values":{},"positionals":[]}
設定 わかったこと
strict: false 定義がなくても受け取る。「=」つきは文字列、それ以外は true になる
allowNegative: true --no-color で color が false になる。付けないと未定義オプション扱い
boolean の multiple -v -v -v が [true, true, true] になる。長さを数えると詳細度の段階に使える
負の数 --num -5 は別のオプションと区別がつかないため止まる。--num=-5 なら通る
未指定で default なし values は空のオブジェクト

tokens: true を付けると、戻り値に tokens 配列が加わります。-v --name a を解析したときの中身は次のとおりでした。

[
  {"kind":"option","name":"v","rawName":"-v","index":0},
  {"kind":"option","name":"name","rawName":"--name","index":1,"value":"a","inlineValue":false}
]

指定された順番や、実際に書かれた名前(rawName)がわかるので、独自の解釈を足したいときに役立ちます。

process.env で環境変数を読む

値はすべて文字列になる

process.env は、環境変数を「名前: 文字列」で持つオブジェクトです。 数値のつもりで設定しても文字列で届きます。

// env.mjs
console.log(typeof process.env.PORT, process.env.PORT);
console.log(typeof process.env.DEBUG, process.env.DEBUG);
console.log(process.env.NOT_SET);
console.log(process.env.PORT + 1);
console.log(Boolean(process.env.DEBUG));
process.env.COUNT = 5;
console.log(typeof process.env.COUNT, process.env.COUNT);
process.env.FLAG = undefined;
console.log(typeof process.env.FLAG, process.env.FLAG);
delete process.env.FLAG;
console.log(process.env.FLAG, 'FLAG' in process.env);
$ PORT=8080 DEBUG=false node env.mjs
string 8080
string false
undefined
80801
true
string 5
string undefined
undefined false
行 結果 意味
process.env.PORT + 1 80801 文字列の連結になる。数値にするには Number を通す
Boolean(process.env.DEBUG) true 文字列 "false" は空ではないので真と判定される
process.env.NOT_SET undefined 未設定の変数は undefined
process.env.COUNT = 5 '5'(string) 代入した値も文字列に変換される
process.env.FLAG = undefined 'undefined'(string) 文字列の "undefined" が入る
delete process.env.FLAG 未設定に戻る 消したいときは delete を使う

DEBUG=false と書いたのに有効になる、というのが最もよくあるつまずきです。 次のように文字列として比較すると意図どおりになります。

$ DEBUG=false node -p "process.env.DEBUG ? \"ON\" : \"OFF\""
ON

$ DEBUG=false node -p "process.env.DEBUG === \"true\" ? \"ON\" : \"OFF\""
OFF

未定義のときの既定値:?? と || の違い

既定値には ??(null 合体演算子)か || を使います。違いは空文字の扱いです。

$ node -p "[process.env.PORT ?? 3000, process.env.PORT || 3000]"
[ 3000, 3000 ]

$ PORT= node -p "[process.env.PORT ?? 3000, process.env.PORT || 3000, Number(process.env.PORT), Number(undefined), parseInt(\"80a\", 10), Number(\"80a\"), Number(\" 80 \")]"
[
  '',  3000, 0,
  NaN, 80,   NaN,
  80
]
状態 process.env.PORT ?? 3000 process.env.PORT || 3000
未設定 3000 3000
空文字(PORT=) ''(空文字のまま) 3000
PORT=8080 '8080' '8080'

数値変換にも気をつける点があります。

式 結果 補足
Number('') 0 空文字は 0 になる。「0番ポート」と解釈されないよう先に空文字を除く
Number(undefined) NaN 未設定をそのまま渡すと NaN
Number('80a') NaN 余計な文字があると NaN になり、気づきやすい
parseInt('80a', 10) 80 読めるところまで読む。打ち間違いに気づきにくい
Number(' 80 ') 80 前後の空白は無視される

設定値の変換は、Number と Number.isInteger の組み合わせが確実です。

コピペ用:型つきで環境変数を読む関数(config.mjs)

「未設定または空文字なら既定値、形式が合わなければ起動時に止める」を1か所にまとめた雛形です。 アプリの他の場所では config だけを参照します。

// config.mjs
function envStr(name, def) {
  const v = process.env[name];
  return v === undefined || v === '' ? def : v;
}
function envInt(name, def) {
  const v = process.env[name];
  if (v === undefined || v === '') return def;
  const n = Number(v);
  if (!Number.isInteger(n)) throw new Error(`${name} は整数で指定してください: "${v}"`);
  return n;
}
function envBool(name, def) {
  const v = process.env[name];
  if (v === undefined || v === '') return def;
  if (['1', 'true', 'yes', 'on'].includes(v.toLowerCase())) return true;
  if (['0', 'false', 'no', 'off'].includes(v.toLowerCase())) return false;
  throw new Error(`${name} は true / false で指定してください: "${v}"`);
}
function envRequired(name) {
  const v = process.env[name];
  if (v === undefined || v === '') throw new Error(`環境変数 ${name} が設定されていません`);
  return v;
}

export const config = {
  port: envInt('PORT', 3000),
  debug: envBool('DEBUG', false),
  logLevel: envStr('LOG_LEVEL', 'info'),
};
console.log(config);
if (process.env.NEED_KEY) console.log(envRequired('API_KEY'));
$ node config.mjs
{ port: 3000, debug: false, logLevel: 'info' }

$ PORT=8080 DEBUG=1 node config.mjs
{ port: 8080, debug: true, logLevel: 'info' }

$ PORT= node config.mjs
{ port: 3000, debug: false, logLevel: 'info' }

$ PORT=80a node config.mjs
file:///home/taro/argv-demo/config.mjs:9
  if (!Number.isInteger(n)) throw new Error(`${name} は整数で指定してください: "${v}"`);
                                  ^

Error: PORT は整数で指定してください: "80a"
    at envInt (file:///home/taro/argv-demo/config.mjs:9:35)
    (以下省略)

$ NEED_KEY=1 node config.mjs
{ port: 3000, debug: false, logLevel: 'info' }
file:///home/taro/argv-demo/config.mjs:21
  if (v === undefined || v === '') throw new Error(`環境変数 ${name} が設定されていません`);
                                         ^

Error: 環境変数 API_KEY が設定されていません
    (以下省略)

起動した瞬間に「どの変数が、どういう形式で必要か」が日本語でわかるので、設定もれの調査が短く済みます。

環境変数を設定する方法(bash・PowerShell・cmd)

シェルによって書き方が違います。bash の行は本記事の環境で実行して確認しました。PowerShell と cmd の行は手元に Windows 環境がないため未実行で、一般に知られた書き方を載せています。

やりたいこと bash / zsh PowerShell(未実行) cmd(未実行)
1回の実行だけ設定 PORT=8080 node app.mjs $env:PORT=8080; node app.mjs set PORT=8080 && node app.mjs
そのシェルの間ずっと設定 export PORT=8080 $env:PORT = "8080" set PORT=8080
値を確認 echo $PORT echo $env:PORT echo %PORT%
設定を消す unset PORT Remove-Item Env:PORT set PORT=
複数まとめて1回だけ PORT=8080 DEBUG=true node app.mjs $env:PORT=8080; $env:DEBUG="true"; node app.mjs set PORT=8080 && set DEBUG=true && node app.mjs

bash での確認結果です。

$ env NODE_ENV=production node -p "process.env.NODE_ENV"
production

$ export GREETING=hi; node -p "process.env.GREETING"; unset GREETING; node -p "process.env.GREETING"
hi
undefined

補足です。

  • bash の PORT=8080 node app.mjs は、その1コマンドにだけ変数を渡します。実行後のシェルには残りません。
  • PowerShell と cmd の「1回だけ」の書き方は、実際にはそのウィンドウの間は値が残ります。
  • cmd の set PORT=8080 && node app.mjs は、&& の前の空白が値に含まれる場合があるとされています。set "PORT=8080" && node app.mjs と引用符で囲む書き方が知られています(未実行)。
  • OS による書き方の違いをなくしたいときは、次の .env ファイルが便利です。

.env ファイルを読み込む(--env-file と process.loadEnvFile)

node --env-file=.env で起動時に読み込む

Node.js は標準で .env ファイルを読み込めます。まず .env を用意します。

# .env
# コメント行
PORT=4000
DEBUG=true
APP_NAME="My App"
GREETING='hello # これは値の一部'
LOG_LEVEL=debug # 行末コメント
EMPTY=
// showenv.mjs
for (const k of ['PORT', 'DEBUG', 'APP_NAME', 'GREETING', 'LOG_LEVEL', 'EMPTY']) {
  console.log(k, '=', JSON.stringify(process.env[k]));
}
$ node showenv.mjs
PORT = undefined
DEBUG = undefined
APP_NAME = undefined
GREETING = undefined
LOG_LEVEL = undefined
EMPTY = undefined

$ node --env-file=.env showenv.mjs
PORT = "4000"
DEBUG = "true"
APP_NAME = "My App"
GREETING = "hello # これは値の一部"
LOG_LEVEL = "debug"
EMPTY = ""
.env の書き方 読み込まれた値 わかること
PORT=4000 "4000" ここでも文字列
APP_NAME="My App" "My App" 引用符は外れる
GREETING='hello # …' "hello # これは値の一部" 引用符の中の # は値の一部
LOG_LEVEL=debug # 行末コメント "debug" 引用符の外の # 以降はコメント
EMPTY= "" 空文字(undefined ではない)

--env-file はスクリプト名より前に書きます(Node.js 自身のオプションだからです)。--env-file .env と空白で区切っても同じ結果でした。

優先順位:シェルの値、あとに書いたファイルの順

$ PORT=9999 node --env-file=.env showenv.mjs | head -1
PORT = "9999"

$ node --env-file=.env --env-file=.env.local showenv.mjs | head -1
PORT = "5000"

(.env.local には PORT=5000 とだけ書いてあります。)

状況 使われる値
シェルで設定済み + .env にもある シェルの値(.env は上書きしない)
--env-file を複数指定 あとに書いたファイルの値

「共通の値は .env、手元だけの上書きは .env.local」という分け方ができます。.env.local のように個人の設定や秘密の値を含むファイルは、.gitignore に入れてリポジトリに含めないようにします。

ファイルがないとき

$ node --env-file=none.env showenv.mjs
node: none.env: not found

$ node --env-file-if-exists=none.env showenv.mjs
none.env not found. Continuing without it.
PORT = undefined
(以下省略)

--env-file は、ファイルがないと起動せずに終了します(終了コードは 9 でした)。 あってもなくてもよいファイルには --env-file-if-exists を使うと、メッセージを出して続行します。

process.loadEnvFile でコードの中から読み込む

起動コマンドを変えたくないときは、コードから読み込めます。

// load.mjs
console.log('読み込み前:', process.env.APP_NAME);
process.loadEnvFile();            // 既定はカレントディレクトリの .env
console.log('読み込み後:', process.env.APP_NAME, process.env.PORT);
try {
  process.loadEnvFile('./none.env');
} catch (err) {
  console.log(err.code, err.message);
}
$ node load.mjs
読み込み前: undefined
読み込み後: My App 4000
ENOENT ENOENT: no such file or directory, open './none.env'

loadEnvFile も、すでにある値は上書きしません。次の2つで確かめました。

$ node --env-file=.env -e "process.loadEnvFile(\".env.local\"); console.log(process.env.PORT)"
4000

$ node -e "process.loadEnvFile(\".env.local\"); process.loadEnvFile(\".env\"); console.log(process.env.PORT)"
5000
方法 複数ファイルで同じ名前があるとき
--env-file を並べる あとに書いたファイルが使われる
process.loadEnvFile を続けて呼ぶ 先に読み込んだ値が残る

順番の意味が逆になるので、loadEnvFile では優先したいファイルを先に読み込みます。

もう1点、import した別モジュールがトップレベルで process.env を読む場合、loadEnvFile より先にそのモジュールが評価されると値がまだ入っていません。設定の読み込みは、エントリーファイルのいちばん最初か、--env-file で行うと順番を気にせずに済みます。

util.parseEnv で文字列だけ解析する

process.env を変更せずに、.env 形式の文字列をオブジェクトにしたいときは util.parseEnv が使えます。

// parse.mjs
import { parseEnv } from 'node:util';
console.log(parseEnv('A=1\nB="two words"\n# c\nC=x # tail'));
$ node parse.mjs
{ A: '1', B: 'two words', C: 'x' }

npm run 経由で引数を渡すときは「--」が必要

package.json にスクリプトを登録して、npm run から雛形を呼んでみます。

{
  "name": "argv-demo",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "cli": "node cli.mjs",
    "start": "node --env-file=.env showenv.mjs",
    "info": "node pk.mjs"
  }
}

「--」なしだと npm がオプションを受け取る

$ npm run cli --name taro in.txt

> argv-demo@1.0.0 cli
> node cli.mjs taro in.txt

{
  port: 3000,
  verbose: false,
  tag: [],
  positionals: [ 'taro', 'in.txt' ]
}

$ npm run cli -n taro -v in.txt
10.9.7
  • 1つ目は、--name が npm 自身のオプションとして受け取られ、スクリプトには taro in.txt だけが届いています(2行目の > node cli.mjs taro in.txt で確認できます)。
  • 2つ目は、-v が npm のバージョン表示として解釈され、スクリプトは実行されずに npm のバージョン 10.9.7 が表示されました。

「--」を挟むとスクリプトに届く

$ npm run cli -- -n taro -v in.txt

> argv-demo@1.0.0 cli
> node cli.mjs -n taro -v in.txt

{
  name: 'taro',
  verbose: true,
  port: 3000,
  tag: [],
  positionals: [ 'in.txt' ]
}

$ npm run cli --silent -- --name taro in.txt
{
  name: 'taro',
  port: 3000,
  verbose: false,
  tag: [],
  positionals: [ 'in.txt' ]
}
書く位置 受け取る相手
「--」より前 npm(--silent など)
「--」より後ろ スクリプト(scripts に書いたコマンドの末尾に追加される)

「npm run 名前 -- 引数」と覚えておくと確実です。 --silent を付けると、> argv-demo@1.0.0 cli の2行が表示されなくなります。

scripts の中で --env-file を使う

起動コマンドに毎回 --env-file を書く代わりに、scripts に入れておけます。

$ npm start

> argv-demo@1.0.0 start
> node --env-file=.env showenv.mjs

PORT = "4000"
DEBUG = "true"
APP_NAME = "My App"
GREETING = "hello # これは値の一部"
LOG_LEVEL = "debug"
EMPTY = ""

npm が追加する環境変数

npm run 経由で起動すると、npm が npm_ で始まる環境変数を追加します。

// pk.mjs
console.log(process.env.npm_package_name, process.env.npm_package_version, process.env.npm_lifecycle_event);
$ npm run info --silent
argv-demo 1.0.0 info

$ node -e "console.log(process.env.npm_package_name, process.env.npm_lifecycle_event)"
undefined undefined

node で直接起動すると undefined になるので、これらに頼る処理は npm run 経由でだけ動く点を覚えておきます。

引数と環境変数の使い分け

観点 コマンドライン引数 環境変数
向いている値 実行のたびに変わるもの(対象ファイル、モード、件数) 置かれた環境で決まるもの(接続先、ポート、ログの詳しさ)
見え方 コマンドに書いた内容がそのまま見える コマンドには現れない
秘密の値 コマンド履歴やプロセス一覧に残るため向かない 引数より向く(.env は .gitignore へ)
打ち間違いの検出 parseArgs の strict で検出できる 自分で確認する(config.mjs の形)
型 すべて文字列 すべて文字列
既定値 options の default ?? や自作関数

両方を受け付けるなら「引数 → 環境変数 → 既定値」の順に優先すると、利用者の予想と合いやすくなります。

// 引数があれば引数、なければ環境変数、どちらもなければ 3000
const port = Number(values.port ?? process.env.PORT ?? 3000);

この形にするときは、options の port から default を外しておきます(default があると values.port が常に入り、環境変数まで進みません)。

つまずきやすい点の一覧

症状 原因 対応
引数の1つ目が node のパスになる process.argv の先頭2つを含めている process.argv.slice(2) を使う
数値の比較や足し算の結果がおかしい 引数も環境変数も文字列 Number() で変換し Number.isInteger() で確認
DEBUG=false なのに有効になる 文字列 "false" は真と判定される === 'true' で比較、または envBool を使う
Unknown option で止まる options に定義がない、またはつづり違い 定義を追加する/つづりを直す
Unexpected argument で止まる allowPositionals が false allowPositionals: true を付ける
--num -5 で止まる ハイフン始まりの値は別のオプションと区別できない --num=-5 と書く
npm run で引数が届かない 「--」がなく npm が受け取っている npm run 名前 -- 引数 と書く
.env の値が反映されない 同じ名前がシェルで設定済み unset 名前 するか、シェル側の値を直す
node: .env: not found で起動しない --env-file のファイルがない パスを直す/--env-file-if-exists を使う
--env-file を書いたのに効かない スクリプト名より後ろに書いている node とスクリプト名の間に書く
PORT= のとき 0 番になる Number('') は 0 空文字を先に除く(envInt の形)
process.env.X = undefined で消えない 文字列 "undefined" が入る delete process.env.X を使う

よくある質問

Q. commander や yargs などのパッケージは必要ですか? A. オプションが数個で、サブコマンドの階層や自動生成のヘルプが不要なら、標準の util.parseArgs で十分に書けます。本記事の雛形(cli.mjs)は追加パッケージなしで、短い名前・既定値・複数指定・エラー表示まで扱えています。サブコマンドごとに別のオプションを持たせたい、ヘルプを自動で整形したい、といった要件が出てきたら専用パッケージを検討する、という順番がおすすめです。

Q. dotenv パッケージは必要ですか? A. Node.js 22 では --env-file と process.loadEnvFile() で .env を読み込めることを本記事で確認しました。起動コマンドか、エントリーファイルの先頭の1行で済みます。使っている Node.js のバージョンでこれらが使えるかは、node --env-file=.env -p "1" を実行してみるとすぐにわかります。

Q. process.env に number や boolean を入れておけますか? A. 入れられません。process.env.COUNT = 5 と代入しても、読み出すと文字列の '5' になります(実行結果のとおりです)。型を持たせたい設定値は、config.mjs のように起動時に一度だけ変換して、通常のオブジェクトに入れておくのがよい方法です。

Q. TypeScript で parseArgs を使うと型はどうなりますか? A. 本記事では TypeScript での確認は未実行です。一般には @types/node を入れると、options の定義から values の型が推論されると知られています。使うときは、手元の @types/node のバージョンで実際の型を確認してください。

まとめ

  • process.argv は文字列の配列で、先頭2つは node とスクリプトのパス。引数は process.argv.slice(2) で取り出します。
  • オプションの解析は標準の util.parseArgs に任せると、短い名前・「=」・まとめ書き・打ち間違いの検出まで引き受けてくれます。type は string と boolean の2種類です。
  • 数値は string で受けて Number と Number.isInteger で確認します。負の数は --num=-5 の形で渡します。
  • process.env の値はすべて文字列です。"false" は真と判定されるので、=== 'true' か変換関数を使います。
  • 既定値は ?? が基本で、空文字も既定値にしたいときは自作の関数にまとめます。
  • .env は node --env-file=.env か process.loadEnvFile() で読み込めます。シェルで設定済みの値が優先され、--env-file を並べたときはあとのファイルが使われます。
  • npm run 経由では「--」の後ろに引数を書きます。「--」がないと npm 自身のオプションとして受け取られます。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Node.jsコマンドライン引数と環境変数|process.argv・env」

株式会社RJCでは、Node.jsのコマンドライン引数と環境変数の扱い方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?