REPL & Node CLI

beginner repl cli debugging node-flags

The Node CLI is more than just node app.js. It’s an interactive playground, a debugger entry point, and a quick scripting tool. Knowing the useful flags saves us a lot of time.

REPL — Read Eval Print Loop

Type node with no arguments and we get an interactive JS shell. Same engine, same APIs as Node, but live.

$ node
Welcome to Node.js v20.11.0.
> 1 + 1
2
> const fs = require("node:fs")
undefined
> fs.readdirSync(".")
[ 'package.json', 'index.js', 'README.md' ]
> .exit

Handy for trying out an API, checking date math, or testing a regex without making a file.

REPL dot commands

Inside the REPL, commands starting with . are special:

  • .help — list all commands
  • .editor — multi-line editor mode (Ctrl+D to finish)
  • .load file.js — evaluate a file’s contents into the REPL
  • .save out.js — save the session to a file
  • .break / .clear — abandon current multi-line input
  • .exit (or Ctrl+D twice) — quit

Useful REPL tricks

  • _ holds the result of the last expression. _error holds the last thrown error.
  • Tab completion works on variables and properties.
  • Top-level await works — no need to wrap in an async function.
> await fetch("https://api.github.com")
> _.status
200

Common CLI flags

-e
execute a string of JS and exit
-p
like -e but prints the result
--watch
auto-restart on file change (Node 18.11+)
--inspect
open Chrome DevTools debugger on port 9229
--inspect-brk
same, but pause on first line
--env-file
load a .env file (Node 20.6+)
--test
run the built-in test runner
--require / -r
preload a module before script runs
--experimental-*
opt into unstable features (loaders, vm modules, ...)

-e and -p — quick one-liners

-e evals a string. -p does the same but prints the result. Great for tiny shell utilities.

# Get a UUID without installing anything
node -p "crypto.randomUUID()"
# d8e7c2a0-...

# Quickly check Node version programmatically
node -p "process.version"

# Read JSON from stdin and pretty-print
cat data.json | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>console.log(JSON.stringify(JSON.parse(s),null,2)))"

—watch — built-in nodemon

Since Node 18.11, we don’t need nodemon for most cases. --watch restarts our process when watched files change.

node --watch server.js
# Watching for file changes...

We can also pass --watch-path=./src to scope it.

—inspect — debugging

Adds a debugger that DevTools can attach to. Open chrome://inspect in Chrome and we see our Node process. Or use VS Code’s “Attach to Node” config.

node --inspect server.js
# Debugger listening on ws://127.0.0.1:9229/...

node --inspect-brk server.js
# Same, but the program pauses on line 1 waiting for us to attach

—env-file — built-in dotenv

Node 20.6+ ships with a built-in .env loader. We no longer need the dotenv package for simple cases.

node --env-file=.env server.js

Inside our code, the variables show up on process.env like normal.

process.argv — reading CLI args

When we write our own CLIs, args come in via process.argv. The first two entries are the node binary and the script path.

// node greet.js Manish
console.log(process.argv);
// [ '/usr/bin/node', '/path/to/greet.js', 'Manish' ]

const name = process.argv[2];
console.log(`Hello, ${name}`);

For anything more than one arg, reach for node:util’s parseArgs (Node 18+) or the commander / yargs packages.

const { parseArgs } = require("node:util");

const { values } = parseArgs({
  options: {
    port: { type: "string", short: "p", default: "3000" },
    dev: { type: "boolean" },
  },
});

console.log(values); // { port: '8080', dev: true }