DEV Community

Orbit Websites
Orbit Websites

Posted on

Building CLI Tools with Node.js: A Step-by-Step Guide for Developers

Building CLI Tools with Node.js: A Step-by-Step Guide for Developers

If you've ever run npm install, git commit, or npx create-react-app, you’ve used a CLI tool. Command-line interfaces are powerful, fast, and scriptable—perfect for automating repetitive tasks, scaffolding projects, or wrapping APIs. And with Node.js, building your own CLI tool is surprisingly straightforward. Let’s walk through how to build one from scratch.


1. Set Up Your Project

Start by initializing a new Node.js project:

mkdir my-cli-tool
cd my-cli-tool
npm init -y
Enter fullscreen mode Exit fullscreen mode

Now, create the main entry file. We’ll call it index.js:

touch index.js
Enter fullscreen mode Exit fullscreen mode

Make it executable by adding a shebang at the top:

#!/usr/bin/env node

console.log('Hello from your CLI tool!');
Enter fullscreen mode Exit fullscreen mode

The shebang (#!/usr/bin/env node) tells the shell to run this file with Node.js.


2. Make It Runnable Locally

To test your CLI without publishing, use npm link. First, tell npm which command should run your script by updating package.json:

{
  "name": "my-cli-tool",
  "bin": {
    "mycli": "index.js"
  },
  "preferGlobal": true
}
Enter fullscreen mode Exit fullscreen mode

Now run:

npm link
Enter fullscreen mode Exit fullscreen mode

This creates a global symlink so you can run mycli anywhere:

mycli
# Output: Hello from your CLI tool!
Enter fullscreen mode Exit fullscreen mode

You now have a working CLI command. Time to make it do something useful.


3. Parse Arguments Like a Pro

Raw process.argv is messy. Use yargs (or alternatives like commander) to handle arguments cleanly.

Install it:

npm install yargs
Enter fullscreen mode Exit fullscreen mode

Update index.js:

#!/usr/bin/env node

const yargs = require('yargs');

yargs
  .command({
    command: 'greet',
    describe: 'Say hello',
    builder: {
      name: {
        describe: 'Your name',
        type: 'string',
        demandOption: true
      }
    },
    handler: (argv) => {
      console.log(`Hello, ${argv.name}!`);
    }
  })
  .help()
  .argv;
Enter fullscreen mode Exit fullscreen mode

Now try it:

mycli greet --name "Alice"
# Output: Hello, Alice!
Enter fullscreen mode Exit fullscreen mode

Yargs gives you:

  • Auto-generated help (--help)
  • Type validation
  • Required flags
  • Nested commands

It’s battle-tested and used in tools like Jest and Webpack. Use it.


4. Add Real Functionality: Example – File Scaffolder

Let’s build something practical: a CLI that scaffolds a simple component.

Add a new command:

const fs = require('fs');
const path = require('path');

yargs
  .command({
    command: 'create <name>',
    describe: 'Create a new component',
    builder: {
      name: {
        describe: 'Component name',
        type: 'string'
      },
      type: {
        describe: 'Component type',
        type: 'string',
        default: 'react'
      }
    },
    handler: (argv) => {
      const { name, type } = argv;
      const dir = path.join(process.cwd(), name);

      if (fs.existsSync(dir)) {
        console.error(`Error: Directory ${name} already exists.`);
        process.exit(1);
      }

      fs.mkdirSync(dir);

      if (type === 'react') {
        fs.writeFileSync(
          path.join(dir, `${name}.jsx`),
          `import React from 'react';\n\nconst ${name} = () => {\n  return <div>${name} Component</div>;\n};\n\nexport default ${name};\n`
        );
      }

      console.log(`${name} created successfully 🚀`);
    }
  })
  // ... rest of yargs config
  .demandCommand(1, 'You need to specify a command')
  .help()
  .argv;
Enter fullscreen mode Exit fullscreen mode

Now run:

mycli create Button --type react
Enter fullscreen mode Exit fullscreen mode

Boom — you just built a mini code generator.


5. Handle Errors Gracefully

Don’t let your CLI crash with a stack trace. Wrap risky operations and exit cleanly:

handler: (argv) => {
  try {
    // ... your logic
  } catch (err) {
    console.error(`Error: ${err.message}`);
    process.exit(1);
  }
}
Enter fullscreen mode Exit fullscreen mode

Also, use console.error() for errors — it goes to stderr, which matters in scripts and pipes.


6. Add Colors and UX Polish

Raw console.log is fine, but a little color goes a long way. Use chalk:

npm install chalk
Enter fullscreen mode Exit fullscreen mode

Then:

const chalk = require('chalk');

console.log(chalk.green(`${name} created successfully 🚀`));
console.error(chalk.red(`Error: ${err.message}`));
Enter fullscreen mode Exit fullscreen mode

You can also use log-update for live rendering (e.g., spinners), but chalk is 90% of what you need.


7. Publish (Optional)

If you want to share your tool:

  1. Make sure the package name is unique.
  2. Add a description, version, and author in package.json.
  3. Run:

Top comments (0)