Node.js CLI Project Structure
Command-line application with TypeScript, commander, and npm publishing setup.
Project Directory
mycli/
package.json
Package config with bin field
tsconfig.json
LICENSE
README.md
.gitignore
.npmignore
Exclude src from npm
src/
index.ts
CLI entry with shebang
cli.ts
Commander program setup
commands/
Subcommand handlers
index.ts
init.ts
mycli init
run.ts
mycli run
lib/
Core business logic
index.ts
config.ts
Config file handling
runner.ts
utils/
index.ts
logger.ts
Colored console output
prompts.ts
Interactive prompts
bin/
Compiled entry point
mycli.js
Built output
tests/
cli.test.ts
commands/
init.test.ts
Why This Structure?
Node.js CLIs leverage the npm ecosystem and are easy to distribute via npx. This structure uses TypeScript for type safety and commander for argument parsing. The bin/ field in package.json enables global installation.
Key Directories
- src/index.ts-Entry point with #!/usr/bin/env node
- src/cli.ts-Commander program and command registration
- src/commands/-One file per subcommand
- bin/-Compiled JS, referenced in package.json bin
Commander Setup
// src/cli.ts
import { Command } from 'commander';
import { initCommand } from './commands/init';
import { runCommand } from './commands/run';
const program = new Command()
.name('mycli')
.description('CLI description')
.version('1.0.0');
program
.command('init ')
.description('Initialize a new project')
.action(initCommand);
program
.command('run')
.option('-c, --config ', 'Config file')
.action(runCommand);
export { program };
Getting Started
npm init -ynpm add commander chalknpm add -D typescript @types/node- Add
"bin": { "mycli": "./bin/mycli.js" }to package.json npm run build && npm link
When To Use This
- Building npm-distributed CLI tools
- Want rich npm ecosystem (chalk, ora, inquirer)
- Need
npx mycliexecution - Interactive prompts and spinners
- JavaScript/TypeScript team
Trade-offs
- Startup time-Node.js has slower cold start than Go/Rust
- Distribution-Requires Node.js installed (or bundle with pkg)
- Dependencies-node_modules can grow large
Best Practices
- Add shebang
#!/usr/bin/env nodeto entry - Use
chalkfor colored output - Use
orafor spinners on long operations - Handle SIGINT gracefully
- Provide
--jsonflag for scriptable output
Naming Conventions
- Commands-One file per command (init.ts, run.ts)
- Exports-Named exports (initCommand, runCommand)
- Package name-Lowercase with hyphens (my-cli)