๐๏ธ Day 1 โ Set Up the Project
15 min readยทJan 1, 2026
Welcome to Day 1.
In today's lesson, you'll set up a clean Node.js project and ship your first working CLI command: lb_notes --help.
You'll learn how a CLI project is laid out, how a command decides what to do based on arguments, and how to run it like a real terminal command.
By the end of this lesson, you'll have a working lb_notes command that prints a help menu and returns proper exit codes.
Check your environment
Before we get started, let's make sure your environment is properly set up.
To complete this 7-day course, you will need:
- A working installation of Node.js version 20 or higher.
- A working installation of npm version 10 or higher.
- A text editor
- A terminal
๐ If you're not sure where to start, open this lesson in a new tab first: Set Up Your Environment, then come back to this one.
Set up the project
Within the directory that contains your Node.js projects, let's create a new directory for this project called lb_notes.
~/projects/
โโ lb_notes/
Notes:
- Your projects directory doesn't have to be
~/projects. Feel free to use any other directory on your machine.- Once created, don't forget to open the project's directory in your text editor.
Within the lb_notes directory, let's create a new file named package.json.
~/projects/
โโ lb_notes/
โโ package.json
๐ The slash character (
/) is only used to indicate a directory and is not part of the file name.
And within this file, let's write the following minimal configuration.
{
"name": "lb_notes",
"version": "1.0.0",
"type": "module"
}
Where:
"name"is the name of the project."version"is the version number of the project."type"is the type of Node.js module syntax in use. Here,"module"indicates that we're using the ECMAScript syntax.
Implement the help() function
Within the project's directory, let's create the following src/cli/commands nested directory structure.
~/projects/
โโ lb_notes/
โโ package.json
โโ src/
โโ cli/
โโ commands/
Where:
srcwill contain the source files of the project.cliwill contain the files related to the CLI tool.commandswill contain the files related to the commands executed by the CLI tool.
Within the commands directory, let's create a new file named help.js.
~/projects/
โโ lb_notes/
โโ package.json
โโ src/
โโ cli/
โโ commands/
โโ help.js
Within this file, let's export the following function used to output the help menu of the lb_notes command, with a format similar to the one used in Unix man pages.
export default function help() {
console.log(`NAME
LBNotes โ a tiny CLI notes tool
SYNOPSIS
lb_notes [-h|--help]
Output this help menu.`);
}
Which when executed will produce this output.
~/projects/lb_notes$ lb_notes --help
NAME
LBNotes โ a tiny CLI notes tool
SYNOPSIS
lb_notes [-h|--help]
Output this help menu.
Implement the command parser
Within the src/cli directory, let's create a new file named parse_args.js.
~/projects/
โโ lb_notes/
โโ package.json
โโ src/
โโ cli/
โโ commands/
โ โโ help.js
โโ parse_args.js
Within this file, let's export the following function used to parse the command-line arguments supplied to the script, and return a object that describes the command to execute.
export default function parseArgs(args) {
if (!args || !args.length || args[0] === '-h' || args[0] === '--help') {
return {
command: 'help'
};
}
throw new Error(`${args[0]}: Unknown command`);
}
Where:
argsis an array of values that represent the list of command-line flags and arguments supplied to the script upon execution.
When executed, this function will either:
- Return an object that describes the command to execute if the
argsarray isundefined, empty, or if its first element is either the string"-h"or"--help". - Otherwise, throw an error indicating that the specified command is unknown.
Implement the CLI router
Within the src/cli directory, let's create a new file named index.js.
~/projects/
โโ lb_notes/
โโ package.json
โโ src/
โโ cli/
โโ commands/
โ โโ help.js
โโ index.js
โโ parse_args.js
Within this file, let's export the following function used to execute the appropriate function from the commands directory based on the object returned by the parseArgs() function.
import parseArgs from './parse_args.js';
import help from './commands/help.js';
export default function run(args) {
try {
const { command } = parseArgs(args);
if (command === 'help') {
help();
}
} catch(error) {
console.error(`lb_notes: error: ${error.message}`);
return 1;
}
return 0;
}
Where:
argsis an array of values that represent the list of command-line flags and arguments supplied to the script upon execution.
When executed, this function will either:
- Execute the function corresponding to the name of the command parsed from the list of command-line arguments โ in this case the
help()function โ and return0indicating that the command was successfully executed. - Otherwise, catch and log any error raised by the
parseArgs()orhelp()functions, and return1indicating that something went wrong.
Implement the CLI entry point
Within the project's directory, let's create a new directory named bin (short for binary).
~/projects/
โโ lb_notes/
โโ bin/
โโ package.json
โโ src/
โโ cli/
โโ commands/
โ โโ help.js
โโ index.js
โโ parse_args.js
Within the bin directory, let's create a new file named lb_notes.js.
~/projects/
โโ lb_notes/
โโ bin/
โ โโ lb_notes.js
โโ package.json
โโ src/
โโ cli/
โโ commands/
โ โโ help.js
โโ index.js
โโ parse_args.js
And make this file executable using the chmod command.
~/projects/lb_notes$ chmod +x bin/lb_notes.js
Within the lb_notes.js file, let's implement the entry point of the CLI tool, which is the main file that will be executed by Node.js when running the lb_notes command in the terminal.
#!/usr/bin/env node
import run from '../src/cli/index.js';
const exitCode = run(process.argv.slice(2));
process.exit(exitCode);
When executed, this module will:
- Execute the
run()function defined in the previous step with the command-line arguments supplied to the script, accessible through the globalprocess.argvvariable. - Terminate the process with the exit code returned by the
run()function.
Notes:
- In Node.js, the first 2 elements of the
process.argvarray are the absolute paths to thenodebinary used to execute the script and the script itself. Since we have no use for them here, we can remove them from the array using the.slice()method.- In Unix-like operating systems, the exit code is an integer returned by a command-line program used to indicate its successful or failed execution. By convention,
0means success and any other number means failure. In Node.js, the exit code of a script is set using theprocess.exit()function.
Let's then add the following "bin" property to the package.json file that indicates the name of the CLI tool and the path to its entry point module
{
"name": "lb_notes",
"version": "1.0.0",
"type": "module",
"bin": {
"lb_notes": "bin/lb_notes.js"
}
}
Finally, let's use the npm link command to create a symbolic link from the entry point to the global node_modules folder, allowing us to run the bin/lb_notes.js script from the project's directory using the lb_notes command.
~/projects/lb_notes$ npm link
Test the CLI
Let's now verify that the lb_notes command successfully outputs the help menu when executed:
-
Without command-line arguments:
~/projects/lb_notes$ lb_notes NAME LBNotes โ a tiny CLI notes tool SYNOPSIS lb_notes [-h|--help] Output this help menu. -
With the
-hflag:~/projects/lb_notes$ lb_notes -h NAME LBNotes โ a tiny CLI notes tool SYNOPSIS lb_notes [-h|--help] Output this help menu. -
With the
--helpflag:~/projects/lb_notes$ lb_notes --help NAME LBNotes โ a tiny CLI notes tool SYNOPSIS lb_notes [-h|--help] Output this help menu.
And let's verify that the lb_notes command outputs an error message when executed with an invalid command-line argument.
~/projects/lb_notes$ lb_notes -x
lb_notes: error: -x: Unknown command
You're done for today โ