🌐 Translation: Translated from Korean.

How to Build Node.js CLI Tools from Scratch: Complete Guide with Commander.js & TypeScript

Are you tired of repetitive tasks slowing down your development workflow? Need a custom tool you can share with your team? Node.js CLI (Command Line Interface) tools are powerful assets that can dramatically boost developer productivity. In this comprehensive guide, we’ll walk through building professional CLI tools with Node.js and TypeScript, step by step. The best part? Node.js CLI development is approachable for beginners and immediately applicable in real-world scenarios.

Why Build Your Own CLI Tools?



Many developers spend significant time on repetitive tasks—creating files, applying code templates, running deployment scripts, and more. These operations can all be automated with custom CLI tools. Node.js is an ideal platform for CLI development thanks to its rich ecosystem and cross-platform support.

What you’ll learn:

  • Fundamental architecture of Node.js CLI tools

  • Command parsing with Commander.js

  • Type safety with TypeScript

  • Complete, production-ready examples you can use immediately

Core Concepts of CLI Development

What is a Node.js CLI Tool?

A Node.js CLI tool is a terminal-based program that accepts commands and options from users to perform specific tasks. Popular tools like npm, git, and docker are all CLI applications. With TypeScript, you can build safer, more maintainable CLI tools.

Why Choose Commander.js?

Commander.js is one of the most popular libraries for Node.js CLI development. Key advantages include:

  • Clean API: Intuitive and easy-to-learn interface

  • Auto-generated help: Built-in --help option support

  • Type safety: Seamless TypeScript integration

  • Active community: Millions of weekly npm downloads

The Case for TypeScript

Using TypeScript provides several benefits:

  • Catch type errors early during development

  • Boost productivity with IDE autocomplete

  • Safe refactoring with confidence

  • Clear interfaces for team collaboration

Hands-On: Building a File Management CLI Tool

Let’s build a practical file management CLI tool step by step.

Step 1: Project Setup

Create a new project and install required packages:

mkdir my-cli-tool
cd my-cli-tool
npm init -y
npm install commander
npm install -D typescript @types/node tsx

Create a TypeScript configuration file (tsconfig.json):

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Add the following to your package.json:

{
  "name": "my-cli-tool",
  "version": "1.0.0",
  "description": "CLI tool for file management",
  "main": "dist/index.js",
  "bin": {
    "mytool": "dist/index.js"
  },
  "scripts": {
    "build": "tsc",
    "dev": "tsx src/index.ts",
    "start": "node dist/index.js"
  },
  "keywords": ["cli", "file-management", "automation"],
  "author": "Your Name",
  "license": "MIT",
  "dependencies": {
    "commander": "^11.1.0",
    "inquirer": "^9.2.0",
    "ora": "^7.0.0",
    "zod": "^3.22.0"
  },
  "devDependencies": {
    "@types/node": "^20.10.0",
    "typescript": "^5.3.0",
    "tsx": "^4.7.0"
  }
}

💡 Tip: The bin field defines your CLI command name. Here we’ve set it to mytool.

Step 2: Build the Basic CLI Structure

Create src/index.ts with the basic structure:

#!/usr/bin/env node

import { Command } from 'commander';
import * as fs from 'fs';
import * as path from 'path';

const program = new Command();

program
  .name('mytool')
  .description('CLI tool for file management')
  .version('1.0.0');

// File creation command
program
  .command('create <filename>')
  .description('Create a new file')
  .option('-c, --content <text>', 'File content')
  .action((filename: string, options: { content?: string }) => {
    try {
      const content = options.content || '// New file\n';
      fs.writeFileSync(filename, content, 'utf-8');
      console.log(`✅ File created: ${filename}`);
    } catch (error) {
      console.error(`❌ File creation failed: ${error instanceof Error ? error.message : String(error)}`);
      process.exit(1);
    }
  });

// File list command
program
  .command('list [directory]')
  .description('Display files in a directory')
  .option('-a, --all', 'Include hidden files')
  .action((directory: string = '.', options: { all?: boolean }) => {
    try {
      const files = fs.readdirSync(directory);
      const filteredFiles = options.all
        ? files
        : files.filter(file => !file.startsWith('.'));

      console.log(`\n📁 Directory: ${path.resolve(directory)}\n`);
      filteredFiles.forEach(file => {
        const stats = fs.statSync(path.join(directory, file));
        const icon = stats.isDirectory() ? '📂' : '📄';
        console.log(`  ${icon} ${file}`);
      });
      console.log(`\nTotal: ${filteredFiles.length} items\n`);
    } catch (error) {
      console.error(`❌ List operation failed: ${error instanceof Error ? error.message : String(error)}`);
      process.exit(1);
    }
  });

program.parse(process.argv);

⚠️ Important: The shebang line #!/usr/bin/env node at the top is required. Without it, the shell won’t execute your tool.

Step 3: Build and Test

Compile TypeScript to JavaScript:

npm run build

Use npm link to test locally:

npm link

Now you can use your CLI tool from the terminal:

# Create a file
mytool create test.js --content "console.log('Hello');"

# List files
mytool list

# List files including hidden ones
mytool list --all

# Show help
mytool --help

Production-Ready Tips

Error Handling

Always use try-catch blocks in your commands to handle errors gracefully. Providing clear error messages to users is crucial.

Input Validation

Use libraries like Zod to validate user input.

First, install Zod:

npm install zod

Example usage:



import { z } from 'zod';

const fileNameSchema = z.string().regex(/^[\w\-. ]+$/);

// Use in your command
const validatedName = fileNameSchema.parse(filename);

Interactive Prompts

For complex input scenarios, use inquirer to provide an interactive interface.

Installation:

npm install inquirer

Progress Indicators

For long-running operations, display a spinner using ora.

First, install ora:

npm install ora

Example usage:

import ora from 'ora';

const spinner = ora('Creating file...').start();
// Perform operation
spinner.succeed('File created successfully!');

Additional Considerations

Distribution

Publish your package to npm so anyone can install it with npm install -g my-cli-tool. Before publishing, ensure:

  • Unique package name (check availability on npm)

  • Comprehensive README.md (usage instructions and examples)

  • LICENSE file included

  • .npmignore to exclude unnecessary files

Testing

CLI tools need testing too. Write unit tests using Vitest or Jest.

Cross-Platform Compatibility

Ensure your tool works on Windows, macOS, and Linux:

  • Use path.join() for path separators

  • Consider platform differences when handling file permissions

  • Use cross-spawn when executing shell commands

Conclusion

With Node.js and Commander.js, you can quickly develop professional CLI tools. TypeScript-powered Node.js CLI development offers both type safety and productivity. Let’s recap what we covered:

  • Project setup: Creating a structured Node.js CLI project with TypeScript and Commander.js

  • Command implementation: Defining functionality with command() and option()

  • Production tips: Error handling, input validation, and progress indicators

Next steps:

  • Try running the examples above yourself

  • Add commands to automate your own repetitive tasks

  • Publish to npm and share with your team

Series continuation:

Now that you’ve mastered Node.js CLI development, it’s time to apply it to real projects! In the next article, we’ll explore building an automated publishing system integrated with WordPress. Manage your blog content automatically with CLI tools.

Additional resources:

  • Commander.js official documentation

  • Node.js official documentation

  • TypeScript handbook

Have questions about Node.js CLI development? Leave a comment below!

💡 All code in this article has been tested and is production-ready. For more complex examples, check out various use cases in the project’s GitHub repository.