DEV Community

Cover image for Creating a CLI Tool with Node.js
Hasan Ashab
Hasan Ashab

Posted on

Creating a CLI Tool with Node.js

Command-line tools might not have fancy interfaces, but they’re some of the most powerful and time-saving tools a developer can build. From simple automation scripts to complex developer utilities, CLIs (Command Line Interfaces) are at the heart of how modern developers work.

In this post, we’ll walk through how to build a robust CLI with Node.js, and we’ll take a close look at SamerArtisan — a Laravel Artisan–inspired framework that brings structure, elegance, and TypeScript support to CLI development.


Why Build a CLI Tool?

Think about how often you type git, npm, or docker in your terminal. CLI tools let us control complex systems quickly, script them easily, and automate repetitive tasks.

Here’s why developers love CLIs:

  • ⚙️ Automation-friendly – Easily scriptable and perfect for CI/CD.
  • 💡 Lightweight – No need for a GUI, ideal for servers and terminals.
  • 🧩 Composable – Can be chained with other tools using pipes and redirects.
  • Fast – Interacts directly with the system, no browser overhead.

In short: CLIs make developers faster.


The Node.js CLI Ecosystem

Node.js has become a favorite for building CLI tools, thanks to its simplicity and cross-platform nature. The most common library for this is Commander.js, which handles command parsing, flags, and options well.

But as your tool grows, Commander.js can start to feel repetitive — lots of boilerplate, little structure. That’s where SamerArtisan comes in.

Inspired by Laravel’s “Artisan” console, SamerArtisan gives Node.js CLIs a framework-like experience — complete with classes, structure, interactivity, and first-class TypeScript support.


Commander.js vs SamerArtisan — Which Should You Use?

🧱 Commander.js: The Classic Choice

Commander.js is perfect when you need:

  • A simple CLI with a few commands.
  • A quick prototype or internal tool.
  • Minimal dependencies and maximum control.

Example:

const { Command } = require('commander');
const program = new Command();

program
  .name('my-cli')
  .description('CLI for simple greetings')
  .version('0.8.0');

program
  .command('greet')
  .description('Greet someone')
  .argument('<name>', 'person to greet')
  .option('-u, --uppercase', 'uppercase the greeting')
  .action((name, options) => {
    const greeting = `Hello ${name}!`;
    console.log(options.uppercase ? greeting.toUpperCase() : greeting);
  });

program.parse();
Enter fullscreen mode Exit fullscreen mode

Straightforward — but not very scalable.


💎 SamerArtisan: Structure with Style

SamerArtisan shines when your CLI needs more than a few commands. It gives you:

  • Organized command classes and inheritance.
  • Interactive prompts, progress bars, and tables.
  • TypeScript support for safety and autocompletion.
  • A Laravel-like experience for developers.
  • A scalable foundation for large tools.

Nodejs ClI

Example:

const { SamerArtisan, Command } = require("samer-artisan");

class GreetCommand extends Command {
  signature = "greet {name} {--uppercase}";
  description = "Greet someone";

  handle() {
    const name = this.argument('name');
    const uppercase = this.option('uppercase');
    const greeting = `Hello ${name}!`;
    this.info(uppercase ? greeting.toUpperCase() : greeting);
  }
}

SamerArtisan.add(new GreetCommand());
SamerArtisan.parse();
Enter fullscreen mode Exit fullscreen mode

Cleaner, more readable, and ready to scale.


Getting Started with SamerArtisan

Let’s build a simple project management CLI that initializes projects and manages tasks.

Step 1: Install

npm install samer-artisan
Enter fullscreen mode Exit fullscreen mode

Step 2: Set Up Your CLI Entry File

// cli.js
const { SamerArtisan } = require("samer-artisan");

SamerArtisan
  .projectName("ProjectManager")
  .root(__dirname)
  .load("commands")
  .parse();
Enter fullscreen mode Exit fullscreen mode

Step 3: Create a Command

You can generate a command class automatically:

node cli.js make:command InitProject
Enter fullscreen mode Exit fullscreen mode

This gives you a clean template:

// commands/InitProject.js
const { Command } = require("samer-artisan");

class InitProject extends Command {
 /**
  * The name and signature of the console command.
  */
  signature = `project:init 
        { a: First arg }
        { b }
        { c*: Third arg }
        { --dog : First opt }`;

 /**
  * The console command description.
  */
  description = "command description";

 /**
  * Execute the command
  */
  handle() {
    this.info("InitProject command works!");
  }
}

module.exports = InitProject;
Enter fullscreen mode Exit fullscreen mode

Features That Make SamerArtisan Special

1. Interactive Prompts

SamerArtisan makes user input effortless:

const name = await this.ask('Project name?');
const apiKey = await this.secret('Enter your API key:');
const framework = await this.choice('Choose framework:', ['React', 'Vue', 'Svelte']);
Enter fullscreen mode Exit fullscreen mode

2. Beautiful Output

this.info('✅ All systems operational');
this.warn('⚠️  Some warnings detected');
this.error('❌ Critical issues found');

this.table(
  ['Service', 'Status', 'Uptime'],
  [
    ['Database', '✅ Running', '99.9%'],
    ['API', '⚠️ Slow', '98.1%'],
    ['Cache', '❌ Down', '0%'],
  ]
);
Enter fullscreen mode Exit fullscreen mode

3. TypeScript Support

Type-safe arguments and options right out of the box:

class TypedCommand extends Command<{name: string}, {force: boolean}> {
  signature = "run {name} {--force}";
  handle() {
    const name = this.argument('name');
    const force = this.option('force');
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Easy Organization

SamerArtisan
  .load(['commands/project', 'commands/database'])
  .parse();
Enter fullscreen mode Exit fullscreen mode

Best Practices for CLI Design

✅ Make It Scriptable and Interactive

if (!this.option('yes')) {
  const confirm = await this.confirm('Proceed with deployment?');
  if (!confirm) return;
}
Enter fullscreen mode Exit fullscreen mode

⚠️ Handle Errors Gracefully

try {
  // logic
} catch (e) {
  this.error(`Error: ${e.message}`);
}
Enter fullscreen mode Exit fullscreen mode

🧭 Document Everything

SamerArtisan can generate help text directly from your command signatures — no extra effort needed.


Real-World Example: Database Migrations

Here’s what a realistic command might look like:

class MigrateCommand extends Command {
  signature = `migrate 
    {--rollback : Rollback last migration}
    {--steps=1 : Steps to rollback}
    {--seed : Run seeders after migration}`;
  description = "Run database migrations";

  async handle() {
    const rollback = this.option('rollback');
    const steps = parseInt(this.option('steps') || '1');
    const seed = this.option('seed');

    if (rollback) return this.runRollback(steps);
    await this.runMigrations();
    if (seed) await this.runSeeders();
  }
}
Enter fullscreen mode Exit fullscreen mode

Readable, structured, and clean — exactly how a CLI should feel.


When to Choose SamerArtisan

Use SamerArtisan if you’re building:

  • A complex CLI with multiple commands or subcommands.
  • A team tool that benefits from consistent structure.
  • A TypeScript project where safety matters.
  • A long-term project that will evolve over time.

Stick with Commander.js for:

  • Quick scripts or small utilities.
  • Projects with strict dependency policies.
  • One-off tools where setup speed matters.

Final Thoughts

Building a CLI tool is about more than just parsing commands — it’s about creating a smooth experience for both humans and scripts.

  • Commander.js is ideal for small tools and prototypes.
  • SamerArtisan shines in structured, scalable, developer-focused projects.

At the end of the day, the “best” CLI framework is the one that fits your use case. Start simple, grow smart, and build tools that make developers’ lives easier — because the best CLI is the one people actually use.

Top comments (0)