How Node.js Handles Command Line Arguments: From process.argv to the Source Code
Imagine: you wrote a cool Node.js script that converts images. But editing the code every time to specify the file name is bad form. It's much cooler to run: node convert.js --input photo.jpg --format webp and get the result. How do you make such magic happen? That's exactly the question, one of the most popular on Stack Overflow, that we'll dedicate our time to today.
It seems simple, right? But under the hood of Node.js (and V8 itself), there are several layers that every developer should know about. We won't just show you how to get process.argv — we'll look into the Node.js source code and learn how arguments travel from your terminal to the process environment variables. Let's go!
Why Do You Even Need Command Line Arguments?
Command line arguments (CLI arguments) are the primary way to make your program flexible and reusable without changing the code. Instead of hardcoding, you pass data as input. This is the foundation of almost all DevOps tools, bundlers (Webpack, Vite), and utilities. Knowing how to work with them is a fundamental skill for any JavaScript developer who wants to step beyond the browser.
Level 1: The Basics — process.argv
The simplest and most native way to get arguments in Node.js is to access the global object process.argv. It's an array of strings. Let's jump straight into an example.
File: args-demo.js
// args-demo.jsconsole.log(process.argv);Run it with arguments:
node args-demo.js hello --name=World 42Console output:
[ '/usr/local/bin/node', // 0: path to the Node.js executable '/home/user/args-demo.js', // 1: path to your script 'hello', // 2: first user argument '--name=World', // 3: second argument '42' // 4: third argument]That's the whole magic. process.argv is simply a slice of what the operating system passed to the process. The first two elements are metadata. Everything after index 2 is your arguments. Important: they are all strings. The number 42 came as the string "42".
How to Discard the First Two Elements?
For convenience, destructuring is often used:
// Get only user argumentsconst [, , ...userArgs] = process.argv;console.log(userArgs); // ['hello', '--name=World', '42']Or simply process.argv.slice(2).
Level 2: Manual Parsing — Your Own Wheel
Working with a raw array of strings is inconvenient. We need to distinguish flags (--name) from values (World). Let's write a simple parser to understand the logic.
// simple-parser.jsconst args = process.argv.slice(2);const parsed = {};
for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg.startsWith('--')) { // Remove '--' and look for a value const key = arg.slice(2); const next = args[i + 1]; // If the next argument doesn't start with '--', it's a value if (next && !next.startsWith('--')) { parsed[key] = next; i++; // Skip the next iteration step } else { parsed[key] = true; // Boolean flag } } else if (arg.startsWith('-')) { // Short flags, e.g. -v const key = arg.slice(1); parsed[key] = true; } else { // Just a positional argument if (!parsed._) { parsed._ = []; } parsed._.push(arg); }}
console.log(parsed);// node simple-parser.js --name World -v hello// { name: 'World', v: true, _: ['hello'] }This code is the foundation of libraries like minimist. As you can see, nothing complicated. But in real projects, it's better to use ready-made