๐Ÿ—๏ธ 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.

package.json
{
  "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:

  • src will contain the source files of the project.
  • cli will contain the files related to the CLI tool.
  • commands will 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.

src/cli/commands/help.js
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.

src/cli/parse_args.js
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:

  • args is 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:

  1. Return an object that describes the command to execute if the args array is undefined, empty, or if its first element is either the string "-h" or "--help".
  2. 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.

src/cli/index.js
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:

  • args is 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:

  1. 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 return 0 indicating that the command was successfully executed.
  2. Otherwise, catch and log any error raised by the parseArgs() or help() functions, and return 1 indicating 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.

bin/lb_notes.js
#!/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:

  1. Execute the run() function defined in the previous step with the command-line arguments supplied to the script, accessible through the global process.argv variable.
  2. Terminate the process with the exit code returned by the run() function.

Notes:

  • In Node.js, the first 2 elements of the process.argv array are the absolute paths to the node binary 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, 0 means success and any other number means failure. In Node.js, the exit code of a script is set using the process.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

package.json
{
  "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:

  1. 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.
    
  2. With the -h flag:

    ~/projects/lb_notes$ lb_notes -h
    NAME
        LBNotes โ€” a tiny CLI notes tool
    
    SYNOPSIS
        lb_notes [-h|--help]
            Output this help menu.
    
  3. With the --help flag:

    ~/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 โœ