Lab 15: Building a Node.js CLI Tool
Objective
Build a complete, professional command-line tool in Node.js — parse arguments, add subcommands, handle stdin/stdout, read config files, add color output, and package it as a runnable script.
Background
The command line is where developers live. Node.js is uniquely powerful for CLI tools because it has built-in HTTP, file system, and process APIs, npm access to 2M+ packages, and runs everywhere. Tools like npm, GitHub CLI, Vercel CLI, and Prettier are all Node.js CLI apps. By the end of this lab you'll have built a fully functional file statistics tool from scratch.
Time
50 minutes
Prerequisites
Lab 08 (Modules)
Lab 09 (Node.js File System)
Lab 11 (Regex)
Tools
Node.js 20 LTS
Docker image:
innozverse-js:latest
Lab Instructions
Step 1: The Basics — process.argv and process.exit
Every CLI starts with reading arguments and returning proper exit codes.
💡
process.exit(0)is success, anything else is failure. Shell scripts check$?after every command — returning the right exit code lets your tool compose with pipes,&&, and||in shell scripts. Always exit non-zero on errors.
📸 Verified Output:
Step 2: Reading stdin — Unix Pipe Support
Professional CLIs work with pipes: cat file.txt | your-tool.
💡
process.stdin.isTTYistruewhen running interactively (terminal),undefined/falsy when piped. This pattern lets a tool work both ways:node tool.js file.txtandcat file.txt | node tool.js. Unix philosophy: tools should compose.
📸 Verified Output:
(input: "the quick brown fox jumps over the lazy dog the quick dog")
Step 3: ANSI Colors — Terminal Styling
Add colors without external dependencies using ANSI escape codes.
💡 Always check
process.stdout.isTTYbefore adding colors. When output is piped to a file or another command (node tool.js | grep error), ANSI codes appear as garbage characters. Respecting this is the mark of a well-behaved CLI.
📸 Verified Output:
(colors appear in terminal; shown as plain text here)
Step 4: Config Files — JSON and .env Patterns
Load configuration from files with fallback to defaults.
💡 Config precedence (lowest to highest): hardcoded defaults → config file → environment variables → CLI flags. This is the pattern used by Prettier, ESLint, Webpack, and Babel. It lets users set project-wide config in a file and override per-run with env vars or flags.
📸 Verified Output:
Step 5: Subcommands — Multi-Command CLI
Structure a CLI with subcommands like git commit, npm install.
💡 The subcommand pattern (
git,npm,docker) groups related functionality under one binary. Each subcommand is an independent module — easy to add, test, and document separately. Real CLIs use this same structure: parse the first arg, dispatch to a handler, pass remaining args.
📸 Verified Output:
Step 6: Spinners & Progress — User Feedback
Keep users informed during long operations.
💡
\r(carriage return) moves the cursor to the start of the current line without newline, allowing in-place updates. Combined with ANSI cursor control, this creates smooth animations. Always checkisTTY— animation codes look terrible in log files.
📸 Verified Output:
(spinner animation visible in terminal)
Step 7: Error Handling & Signals — Graceful Shutdown
Handle errors and interrupts professionally.
💡 SIGTERM is how process managers (systemd, Docker, Kubernetes) stop your process — they send SIGTERM and wait, then SIGKILL. Your SIGTERM handler has typically 10–30 seconds to clean up. Handling it gracefully means no corrupted files, closed database connections, and flushed logs.
📸 Verified Output:
Step 8: Complete CLI Tool — filestats
Build the complete filestats tool combining everything from this lab.
Run it on itself to test:
💡 The
#!/usr/bin/env nodeshebang at line 1 lets the OS run your script directly without typingnode:chmod +x filestats.js && ./filestats.js. Combined withnpm linkor adding toPATH, your script becomes a real CLI command available system-wide.
📸 Verified Output:
Verification
Expected: Prints line count and confirms JSON output.
Common Mistakes
Not slicing process.argv
Always process.argv.slice(2) to skip node + script path
Forgetting isTTY check for colors
ANSI codes in piped output = garbage characters
process.exit() without cleanup
Register SIGTERM/SIGINT handlers for graceful shutdown
No exit code on error
Always process.exit(1) on error — shells depend on this
Blocking the event loop with sync IO
Use fs.promises for large files; sync OK for small configs
Summary
You've built a complete professional CLI tool: argument parsing, stdin piping, ANSI colors, config file loading, subcommand dispatch, spinners, signal handling, and a full filestats utility. These skills transfer directly to building developer tools, automation scripts, and deploy pipelines.
🎉 JavaScript Foundations Complete!
You've finished all 15 JavaScript Foundations labs:
Labs 1–4: Language basics, functions, arrays & objects
Labs 5–7: OOP, async/await, error handling
Labs 8–9: Modules, Node.js file system
Labs 10–12: HTTP/fetch, regex, iterators & generators
Labs 13–15: Functional programming, testing, CLI tools
Next: JavaScript Practitioner — Express APIs, databases, TypeScript, and more.
Further Reading
12factor CLI apps — CLI design best practices
Commander.js — popular arg parsing library
Ink — React for CLI UIs
Last updated
