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!');
Make it executable:
chmod +x cli.js
Now run it:
./cli.js
# Output: Hello from your CLI!
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
}
Now install it globally:
npm install -g .
Now you can run:
mycli
# Output: Hello from your CLI!
Boom. You’ve got a globally available CLI tool.
💡 Pro tip: Use
npm linkduring 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);
Run it:
mycli --name Alice --debug
# Output: You passed: [ '--name', 'Alice', '--debug' ]
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
#!/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');
}
Now:
mycli --name Bob --debug
# Hello, Bob!
# Debug mode enabled
Option 2: yargs (more features)
npm install yargs
#!/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');
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"');
}
}
Now run:
mycli init express
☕ Playful
Top comments (0)