
Node.js Command Line Arguments and Node Scripts
Node.js command line arguments and scripting capabilities are fundamental tools that every server-side JavaScript developer should master. These features allow you to create dynamic, configurable applications that can adapt their behavior based on runtime parameters, making your code more flexible and reusable. This post will walk you through the essential techniques for handling command line arguments, creating robust Node.js scripts, and implementing best practices that’ll save you countless hours of debugging and manual configuration.
How Command Line Arguments Work in Node.js
Node.js automatically captures command line arguments through the global process.argv
array. When you execute a Node.js script, the runtime populates this array with the following structure:
// process.argv structure:
// [0] - Path to Node.js executable
// [1] - Path to the script file
// [2+] - Actual command line arguments
console.log(process.argv);
// Running: node script.js --port 3000 --env production
// Output: [
// '/usr/local/bin/node',
// '/path/to/script.js',
// '--port',
// '3000',
// '--env',
// 'production'
// ]
The raw process.argv
gives you everything, but you’ll typically want to work with just the user-provided arguments:
const args = process.argv.slice(2);
console.log('User arguments:', args);
Step-by-Step Implementation Guide
Let’s build a practical example that demonstrates different argument parsing techniques, starting with manual parsing and progressing to more sophisticated approaches.
Basic Manual Parsing
#!/usr/bin/env node
const args = process.argv.slice(2);
const config = {};
// Simple flag detection
if (args.includes('--verbose')) {
config.verbose = true;
}
if (args.includes('--help')) {
console.log('Usage: node script.js [options]');
console.log('Options:');
console.log(' --verbose Enable verbose logging');
console.log(' --port PORT Set server port');
console.log(' --help Show this help message');
process.exit(0);
}
// Key-value pair parsing
for (let i = 0; i < args.length; i++) {
if (args[i] === '--port' && args[i + 1]) {
config.port = parseInt(args[i + 1]);
i++; // Skip next argument since we consumed it
}
if (args[i] === '--env' && args[i + 1]) {
config.environment = args[i + 1];
i++;
}
}
console.log('Configuration:', config);
Using Built-in parseArgs (Node.js 18.3+)
Node.js introduced a built-in argument parser that eliminates the need for external libraries in many cases:
import { parseArgs } from 'node:util';
const options = {
'port': {
type: 'string',
short: 'p',
default: '3000'
},
'verbose': {
type: 'boolean',
short: 'v',
default: false
},
'env': {
type: 'string',
short: 'e',
default: 'development'
},
'help': {
type: 'boolean',
short: 'h',
default: false
}
};
try {
const { values, positionals } = parseArgs({ options });
if (values.help) {
console.log('Usage: node script.js [options]');
console.log('Options:');
Object.entries(options).forEach(([key, opt]) => {
console.log(` --${key}, -${opt.short} ${opt.type} (default: ${opt.default})`);
});
process.exit(0);
}
console.log('Parsed options:', values);
console.log('Positional arguments:', positionals);
} catch (error) {
console.error('Argument parsing error:', error.message);
process.exit(1);
}
Advanced Parsing with External Libraries
For complex applications, consider using yargs
or commander.js
. Here's a commander.js example:
import { Command } from 'commander';
const program = new Command();
program
.name('my-app')
.description('A sample Node.js CLI application')
.version('1.0.0')
.option('-p, --port ', 'server port', '3000')
.option('-v, --verbose', 'enable verbose logging')
.option('-e, --env ', 'application environment', 'development')
.option('-c, --config ', 'configuration file path');
program
.command('start')
.description('Start the server')
.action(() => {
const options = program.opts();
console.log('Starting server with options:', options);
// Server startup logic here
});
program
.command('migrate')
.description('Run database migrations')
.option('--rollback', 'rollback last migration')
.action((cmdOptions) => {
console.log('Running migrations:', cmdOptions);
// Migration logic here
});
program.parse();
Real-World Examples and Use Cases
Database Connection Script
#!/usr/bin/env node
import { parseArgs } from 'node:util';
import mysql from 'mysql2/promise';
const options = {
'host': { type: 'string', default: 'localhost' },
'port': { type: 'string', default: '3306' },
'user': { type: 'string', default: 'root' },
'password': { type: 'string', default: '' },
'database': { type: 'string' },
'query': { type: 'string' },
'dry-run': { type: 'boolean', default: false }
};
const { values } = parseArgs({ options });
if (!values.database || !values.query) {
console.error('Error: --database and --query are required');
process.exit(1);
}
async function executeQuery() {
if (values['dry-run']) {
console.log('DRY RUN - Would execute query:', values.query);
console.log('Against database:', values.database);
return;
}
try {
const connection = await mysql.createConnection({
host: values.host,
port: parseInt(values.port),
user: values.user,
password: values.password,
database: values.database
});
const [results] = await connection.execute(values.query);
console.log('Query results:', results);
await connection.end();
} catch (error) {
console.error('Database error:', error.message);
process.exit(1);
}
}
executeQuery();
File Processing Utility
#!/usr/bin/env node
import fs from 'fs/promises';
import path from 'path';
import { parseArgs } from 'node:util';
const options = {
'input': { type: 'string', short: 'i' },
'output': { type: 'string', short: 'o' },
'format': { type: 'string', default: 'json' },
'recursive': { type: 'boolean', default: false },
'verbose': { type: 'boolean', short: 'v', default: false }
};
const { values } = parseArgs({ options });
async function processFiles() {
if (!values.input) {
console.error('Error: Input path is required (--input or -i)');
process.exit(1);
}
try {
const inputPath = path.resolve(values.input);
const stats = await fs.stat(inputPath);
if (stats.isDirectory()) {
const files = await fs.readdir(inputPath, {
recursive: values.recursive
});
if (values.verbose) {
console.log(`Found ${files.length} files in ${inputPath}`);
}
// Process each file
for (const file of files) {
const filePath = path.join(inputPath, file);
await processFile(filePath);
}
} else {
await processFile(inputPath);
}
} catch (error) {
console.error('Processing error:', error.message);
process.exit(1);
}
}
async function processFile(filePath) {
if (values.verbose) {
console.log(`Processing: ${filePath}`);
}
const content = await fs.readFile(filePath, 'utf8');
// Transform content based on format
let result;
switch (values.format) {
case 'json':
result = JSON.stringify({
file: filePath,
size: content.length,
lines: content.split('\n').length
}, null, 2);
break;
case 'csv':
result = `"${filePath}","${content.length}","${content.split('\n').length}"`;
break;
default:
result = content;
}
if (values.output) {
const outputPath = path.resolve(values.output);
await fs.appendFile(outputPath, result + '\n');
} else {
console.log(result);
}
}
processFiles();
Comparison of Argument Parsing Methods
Method | Complexity | Bundle Size | Features | Best For |
---|---|---|---|---|
Manual parsing | High | 0 KB | Basic flags and values | Simple scripts, minimal dependencies |
parseArgs (built-in) | Medium | 0 KB | Type validation, short flags, defaults | Modern Node.js apps (18.3+) |
yargs | Low | ~200 KB | Commands, validation, auto-help, completion | Feature-rich CLI applications |
commander.js | Low | ~25 KB | Commands, subcommands, type coercion | Clean API, moderate complexity |
minimist | Medium | ~5 KB | Lightweight parsing | Size-conscious applications |
Best Practices and Common Pitfalls
Environment Variable Integration
Combine command line arguments with environment variables for maximum flexibility:
import { parseArgs } from 'node:util';
const options = {
'port': { type: 'string' },
'host': { type: 'string' },
'database-url': { type: 'string' }
};
const { values } = parseArgs({ options });
// Priority: CLI args > Environment variables > Defaults
const config = {
port: values.port || process.env.PORT || '3000',
host: values.host || process.env.HOST || 'localhost',
databaseUrl: values['database-url'] || process.env.DATABASE_URL || 'sqlite://./app.db'
};
console.log('Final configuration:', config);
Input Validation and Error Handling
Always validate your inputs and provide meaningful error messages:
function validateConfig(config) {
const errors = [];
// Port validation
const port = parseInt(config.port);
if (isNaN(port) || port < 1 || port > 65535) {
errors.push(`Invalid port: ${config.port}. Must be between 1-65535`);
}
// URL validation
if (config.databaseUrl) {
try {
new URL(config.databaseUrl);
} catch {
errors.push(`Invalid database URL: ${config.databaseUrl}`);
}
}
// Required fields
if (!config.apiKey && process.env.NODE_ENV === 'production') {
errors.push('API key is required in production mode');
}
if (errors.length > 0) {
console.error('Configuration errors:');
errors.forEach(error => console.error(` - ${error}`));
process.exit(1);
}
return { ...config, port }; // Return with parsed port
}
Script Performance Considerations
For scripts that might process large amounts of data or run frequently:
#!/usr/bin/env node
import { performance } from 'perf_hooks';
const startTime = performance.now();
// Your script logic here
process.on('exit', () => {
const duration = performance.now() - startTime;
if (process.env.TIMING || process.argv.includes('--timing')) {
console.error(`Script execution time: ${duration.toFixed(2)}ms`);
}
});
// Memory usage monitoring
process.on('SIGINT', () => {
const usage = process.memoryUsage();
console.error('\nMemory usage:');
console.error(` RSS: ${Math.round(usage.rss / 1024 / 1024)}MB`);
console.error(` Heap Used: ${Math.round(usage.heapUsed / 1024 / 1024)}MB`);
process.exit(0);
});
Security Considerations
When handling command line arguments, especially in production environments:
- Never log sensitive information like passwords or API keys
- Validate file paths to prevent directory traversal attacks
- Sanitize inputs before using them in shell commands
- Use environment variables for secrets instead of command line arguments
- Implement rate limiting for scripts that make external API calls
import path from 'path';
function sanitizeFilePath(inputPath) {
// Resolve to absolute path and check if it's within allowed directory
const resolvedPath = path.resolve(inputPath);
const allowedDir = path.resolve('./data');
if (!resolvedPath.startsWith(allowedDir)) {
throw new Error(`Access denied: ${inputPath} is outside allowed directory`);
}
return resolvedPath;
}
// Usage
try {
const safePath = sanitizeFilePath(values.input);
// Proceed with file operations
} catch (error) {
console.error('Security error:', error.message);
process.exit(1);
}
Making Scripts Executable
To make your Node.js scripts executable without explicitly calling node
:
#!/usr/bin/env node
// Add this shebang line at the top of your script
// Then make it executable:
// chmod +x your-script.js
//
// Now you can run it directly:
// ./your-script.js --port 8080
For deployment on servers, consider using process managers like PM2 or systemd services. When setting up on a VPS or dedicated server, you can create systemd service files that automatically handle your Node.js scripts with proper argument passing.
Advanced Integration Patterns
Configuration File Support
Combine command line arguments with configuration files for complex applications:
import fs from 'fs/promises';
import path from 'path';
async function loadConfig(configPath) {
try {
const configFile = await fs.readFile(configPath, 'utf8');
return JSON.parse(configFile);
} catch (error) {
if (error.code === 'ENOENT') {
return {}; // Config file doesn't exist, use defaults
}
throw new Error(`Invalid config file: ${error.message}`);
}
}
// Merge configurations with proper precedence
async function buildConfig() {
const { values } = parseArgs({ options });
// Load base configuration
const fileConfig = values.config ?
await loadConfig(values.config) :
await loadConfig('./config.json');
// CLI arguments override file config
return {
...fileConfig,
...Object.fromEntries(
Object.entries(values).filter(([_, value]) => value !== undefined)
)
};
}
Command line arguments and Node.js scripting form the backbone of robust server-side applications and automation tools. By mastering these techniques, you'll build more maintainable, configurable, and professional Node.js applications. The built-in parseArgs
function provides an excellent starting point for modern applications, while libraries like commander.js offer additional features for complex CLI tools.
For comprehensive documentation and additional examples, check out the official Node.js Process documentation and the parseArgs API reference.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.