Skip to content

node_05 - Node.js Command Line

Node is a command line tool before it's a web server
You write files , you run them with node filename.js - that's the core workflow. But the command line has depth: flags that change behavior , arguments you pass to your scripts , environment variables , and the REPL for interactive tinkering. Understanding these means reading error messages correctly , debugging efficiently , and writing scripts that feel native

running files

The simplest invocation:

node app.js

Node reads app.js , compiles it with V8 , and executes it. If the file doesn't exist or has a syntax error , Node will tell you exactly where it failed:

node nonexistent.js
# Error: Cannot find module '/home/user/nonexistent.js'

node syntax-error.js
# SyntaxError: Unexpected token '}'
#     at ... syntax-error.js:5:1

Multiple files? Just run the entry point. Node handles the rest via require():

node server.js
# server.js internally requires routes.js, db.js, config.js
# Node resolves and loads each one as needed

passing arguments - process.argv

Every argument after the filename ends up in process.argv:

// args.js
console.log(process.argv)

// $ node args.js --port 3000 --env production
// Output:
// [
//   '/usr/local/bin/node',    // argv[0] - Node binary path
//   '/home/user/args.js',     // argv[1] - script path
//   '--port',                  // argv[2] - your arguments start here
//   '3000',
//   '--env',
//   'production'
// ]

Parse them manually for simple scripts or use a library (yargs , commander) for anything complex:

// Manual parsing for small scripts
function parseArgs() {
  const args = process.argv.slice(2)
  const config = {}

  for (let i = 0; i < args.length; i++) {
    if (args[i].startsWith('--')) {
      const key = args[i].slice(2)
      const value = args[i + 1] && !args[i + 1].startsWith('--')
        ? args[i + 1]
        : true
      config[key] = value
      if (value !== true) i++ // skip the value on next iteration
    }
  }

  return config
}

const config = parseArgs()
console.log('Config:' , config)

// $ node parse.js --port 3000 --verbose
// Config: { port: '3000', verbose: true }

Security note: Never pass sensitive data as command line arguments. On Linux , any user can see process arguments via ps aux or /proc/<pid>/cmdline. That --db-password hunter2 is visible to every user on the system

# Anyone on the system can see this
ps aux | grep node
# That database password is in plain view

useful CLI flags

# Watch mode (Node 18+ experimental, stable in Node 22)
# Restarts when files change
node --watch app.js

# Evaluate inline code without a file
node -e "console.log(2 + 2)"
# 4

# Inspect mode - attach Chrome DevTools
node --inspect app.js
# Open chrome://inspect in Chrome to debug

# Break at the first line of code
node --inspect-brk app.js

# Memory and performance
node --max-old-space-size=4096 app.js  # increase heap to 4GB
node --prof app.js                     # generate V8 profiling log

# Security-focused flags
node --jitless app.js                  # disable JIT (mitigates JIT-based exploits)
node --experimental-policy=policy.json app.js # enforce dependency integrity

The --jitless flag disables V8's JIT compiler entirely , which prevents JIT spraying attacks and Spectre-style side channels. Performance drops significantly but for security-critical applications it's worth knowing about

environment variables - process.env

Environment variables are key-value pairs injected into the process at startup:

// Access them via process.env
const dbHost = process.env.DB_HOST || 'localhost'
const dbPort = parseInt(process.env.DB_PORT , 10) || 5432
const nodeEnv = process.env.NODE_ENV || 'development'

console.log(`Connecting to ${dbHost}:${dbPort} in ${nodeEnv} mode`)

Set them inline before the node command:

DB_HOST=prod-db.example.com DB_PORT=5432 NODE_ENV=production node app.js

Or export them in your shell profile for development:

# ~/.bashrc or .env file
export DB_HOST=localhost
export DB_PORT=5432

Critical rule: process.env values are ALWAYS strings. process.env.PORT is "3000" not 3000. Always coerce numeric values:

const PORT = parseInt(process.env.PORT , 10) || 3000  // correct
// const PORT = process.env.PORT || 3000               // wrong - "3000" is a string

shebang scripts

Make Node files executable like any other shell script:

#!/usr/bin/env node
// myscript.js - make this executable

const args = process.argv.slice(2)
console.log('Args received:' , args)
chmod +x myscript.js
./myscript.js --foo bar
# Args received: [ '--foo', 'bar' ]

The #!/usr/bin/env node shebang uses env to find the Node binary in the user's PATH , which respects nvm and other version managers. Never hardcode the path like #!/usr/local/bin/node - that breaks on systems where Node is installed elsewhere

the REPL

Typing node with no arguments drops you into an interactive JavaScript shell:

$ node
Welcome to Node.js v22.14.0
> .help
.break    -打断当前命令
.clear    -清除REPL的上下文
.editor   -进入编辑器模式
.exit     -退出REPL
.help     -打印帮助信息
.load     -从文件加载JS
.save     -将REPL会话保存到文件
> 

The REPL evaluates every line and prints the result. Useful for testing small snippets:

> const crypto = require('crypto')
undefined
> crypto.randomBytes(16).toString('hex')
'7a3b9f1c2d8e4f6a0b5c3d7e9f1a2b3c'
> .exit

Tab completion works in the REPL - type process. and press Tab to see all available methods

common pitfalls

Argument order matters - flags must come before the script name:

# Correct
node --watch app.js

# Wrong - --watch is consumed as an argument to app.js
node app.js --watch

Sensitive data in process.env - env vars leak to child processes by default:

const { spawn } = require('child_process')

// BAD - child inherits ALL env vars including secrets
spawn('some-binary')

// GOOD - pass only what the child needs
spawn('some-binary' , [] , {
  env: { PATH: process.env.PATH }
})

prerequisites

node_04 - Node.js vs Browser


next -> node_06_v8_engine.md