JavaScript
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
147: Building a Basic CLI Tool with Node
When I first started building tools for my own workflow, I fell into a trap that I see a lot of developers fall into today: I thought that to build a "real" CLI tool, I had to start by installing a massive library like Commander or Yargs. I figured that since these tools are industry standard, the "correct" way to handle command-line arguments was through a framework.
Thinking you need a framework for basic arguments
Here is why that mindset is a mistake. If you're building a tool that just takes one or two arguments—like a filename or a search term—adding a dependency just to parse a string is overkill. It bloats your project and adds a layer of abstraction over something Node.js already gives you for free.
Take a look at this. If you run a script like node count-words.js my-file.txt, Node doesn't just ignore my-file.txt. It stores it in a built-in array. If you console.log(process.argv), you'll see something like this:
[
'/usr/bin/node',
'/Users/me/projects/count-words.js',
'my-file.txt'
]
The first two elements are always the path to the Node executable and the path to the script being run. Everything after that is your actual user input. You don't need a framework to access process.argv[2]. You just need a basic understanding of how Node handles the process.
Using process.argv and the magic shebang line
Now, typing node script.js every single time is tedious. You want to be able to just type count-words my-file.txt from anywhere in your terminal. To do this, you need to tell the operating system that this file should be executed using Node, regardless of the file extension.
This is where the "shebang" comes in. By adding #!/usr/bin/env node to the very first line of your file, you're giving the shell a hint. I've seen people forget this and spend an hour wondering why their script works with node ... but fails with a "command not found" or "permission denied" error when run directly.
#!/usr/bin/env node
const fs = require('fs');
const filePath = process.argv[2];
if (!filePath) {
console.error('Please provide a file path!');
process.exit(1);
}
try {
const content = fs.readFileSync(filePath, 'utf8');
const words = content.trim().split(/\s+/).length;
console.log(`Word count: ${words}`);
} catch (err) {
console.error('Could not read file:', err.message);
process.exit(1);
}
Turning a script into a global command via package.json
The final piece of the puzzle isn't in the code, but in the configuration. To make your tool available globally on your machine, you use the bin field in your package.json. This creates a symbolic link from your script to a folder that's already in your system's PATH.
Inside your package.json, add a mapping where the key is the command name you want to use and the value is the path to your script:
{
"name": "word-count-cli",
"version": "1.0.0",
"bin": {
"count-words": "./count-words.js"
}
}
After saving this, run npm link in your terminal. This is a developer's secret weapon. It tells npm to link the current package globally. Now, you can navigate to any folder on your hard drive, type count-words some-text-file.txt, and it will just work. It feels like a native system tool, but it's just a few lines of JavaScript and a clever symlink.
📋 Practical Task
Build a 'Log-Keyword-Filter' CLI Tool
Your task is to build a CLI tool that reads a log file and prints only the lines that contain a specific keyword (e.g., "ERROR" or "WARN").
- Requirement 1: The tool must take two arguments: the path to a text file and the keyword to search for.
- Requirement 2: Include the shebang line at the top of your script.
- Requirement 3: If the user forgets to provide either the file path or the keyword, the tool should print a helpful usage message and exit with a non-zero status code.
- Requirement 4: Configure the
binfield inpackage.jsonso the tool can be run aslog-filter.
Test your tool: Create a dummy app.log file with several lines of text, then run log-filter app.log ERROR to ensure only the error lines are displayed.
There are no comments for now.