DEV Community

Orbit Websites
Orbit Websites

Posted on

Building CLI Tools with Node.js: A Practical Guide

Building CLI Tools with Node.js: A Practical Guide

If you’ve ever typed npm install, git commit, or npx create-react-app, you’ve used a CLI tool. Command-line interfaces are fast, scriptable, and essential in modern development workflows. With Node.js, building your own CLI tools is surprisingly straightforward — and way more useful than you might think.

Whether it’s automating repetitive tasks, wrapping APIs, or just making your team’s life easier, a well-crafted CLI tool can save hours every week. Let’s cut the fluff and build one from the ground up.


1. Start with #!/usr/bin/env node

This line, called a shebang, tells the shell to run your script with Node.js. Without it, your script is just a .js file. With it, it becomes an executable.

Create a file called cli.js:

#!/usr/bin/env node

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

Make it executable:

chmod +x cli.js
Enter fullscreen mode Exit fullscreen mode

Now run it:

./cli.js
# Output: Hello from your CLI!
Enter fullscreen mode Exit fullscreen mode

That’s your CLI. Seriously.


2. Make it globally available with package.json

You don’t want to run ./cli.js every time. You want to type mycli from anywhere.

Update your package.json:

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

Now install it globally:

npm install -g .
Enter fullscreen mode Exit fullscreen mode

Now you can run:

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

Boom. You’ve got a globally available CLI tool.

💡 Pro tip: Use npm link during development. It creates a symlink so changes reflect immediately.


3. Parse Arguments with process.argv

Node gives you raw access to command-line arguments via process.argv. The first two are node and your script path, so your args start at index 2.

#!/usr/bin/env node

const args = process.argv.slice(2);
console.log('You passed:', args);
Enter fullscreen mode Exit fullscreen mode

Run it:

mycli --name Alice --debug
# Output: You passed: [ '--name', 'Alice', '--debug' ]
Enter fullscreen mode Exit fullscreen mode

It works, but it’s messy. You don’t want to parse flags manually.


4. Use a CLI Parser (Like minimist or yargs)

Stop reinventing the wheel. Use a library.

Option 1: minimist (lightweight)

npm install minimist
Enter fullscreen mode Exit fullscreen mode
#!/usr/bin/env node

const argv = require('minimist')(process.argv.slice(2));

if (argv.name) {
  console.log(`Hello, ${argv.name}!`);
}

if (argv.debug) {
  console.log('Debug mode enabled');
}
Enter fullscreen mode Exit fullscreen mode

Now:

mycli --name Bob --debug
# Hello, Bob!
# Debug mode enabled
Enter fullscreen mode Exit fullscreen mode

Option 2: yargs (more features)

npm install yargs
Enter fullscreen mode Exit fullscreen mode
#!/usr/bin/env node

const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');

const argv = yargs(hideBin(process.argv))
  .option('name', {
    alias: 'n',
    type: 'string',
    description: 'Your name'
  })
  .option('debug', {
    alias: 'd',
    type: 'boolean',
    description: 'Enable debug mode'
  })
  .help()
  .argv;

console.log(`Hello, ${argv.name || 'World'}!`);
if (argv.debug) console.log('Debug: ON');
Enter fullscreen mode Exit fullscreen mode

Now you get auto-generated help (--help) and better validation.


5. Add Real Functionality: Example – A File Generator

Let’s build something useful: a CLI that generates a basic Express server.

#!/usr/bin/env node

const fs = require('fs');
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');

const argv = yargs(hideBin(process.argv))
  .command('init <type>', 'Initialize a project', (yargs) => {
    yargs.positional('type', {
      describe: 'Project type (e.g. express)',
      type: 'string'
    });
  })
  .help()
  .argv;

if (argv._[0] === 'init') {
  if (argv.type === 'express') {
    fs.writeFileSync('server.js', `
const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send('Hello from Express!');
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});
    `.trim());

    fs.writeFileSync('package.json', JSON.stringify({
      name: 'my-express-app',
      version: '1.0.0',
      scripts: {
        start: 'node server.js'
      },
      dependencies: {
        express: "^4.18.0"
      }
    }, null, 2));

    console.log('✅ Express app created! Run "npm install" and "npm start"');
  } else {
    console.log('Unsupported project type. Try "express"');
  }
}
Enter fullscreen mode Exit fullscreen mode

Now run:

mycli init express
Enter fullscreen mode Exit fullscreen mode

Playful

Top comments (0)