Why TypeScript is the ideal language for CLI utilities?
\nIf you write in Python and suddenly think about creating a console utility, most likely your first impulse is to use argparse or click. And that makes sense. But what if I tell you that TypeScript can outshine any Python tool in the CLI world? Yes, you heard that right.
TypeScript is not just "JavaScript with types". It is a language that turns CLI utility development into a predictable and safe process. No more "why do I have undefined instead of a string?" or "I passed a number, but it became a string". Strong typing eliminates a whole class of errors at the compilation stage.
But the main advantage is the ecosystem. Node.js allows you to run TypeScript code anywhere: on a server, in Docker, on a Raspberry Pi. And npm makes publishing a utility a five-minute task. Today we will go through the entire path: from an empty folder to a ready-made CLI utility that can be installed with the npm install -g command.
What are we going to build?
\nImagine: you often work with JSON files and need to quickly count the number of fields, check validity, or convert to CSV. We will write a json-toolkit utility that does three things:
- \n
- validate — checks JSON for validity; \n
- count — counts the number of top-level keys; \n
- to-csv — converts an array of objects to CSV. \n
Sounds simple? Yes. But with this example, you will master all the key techniques: argument handling, file system operations, terminal colors, and error handling.
\n\nStep 1. Project setup
\nFirst, create a folder and initialize the project:
\nmkdir json-toolkit\ncd json-toolkit\nnpm init -y\nNow install TypeScript and the necessary packages:
\nnpm install -D typescript @types/node\nnpm install commander chalk\n- \n
commander— a library for parsing command line arguments (similar to Python's argparse, but more convenient); \n chalk— colors the terminal output; \n @types/node— types for Node.js. \n
Create tsconfig.json:
{\n "compilerOptions": {\n "target": "ES2020",\n "module": "commonjs",\n "outDir": "./dist",\n "rootDir": "./src",\n "strict": true,\n "esModuleInterop": true,\n "declaration": true\n },\n "include": ["src/**/*"]\n}\nNote: outDir is the folder where the code will be compiled, and rootDir is the source code. This is standard practice for TypeScript projects.
Step 2. Writing the utility core
\nCreate the src folder and the file src/index.ts. Let's start with imports and commander setup:
import { Command } from 'commander';\nimport { readFileSync, existsSync } from 'fs';\nimport chalk from 'chalk';\n\nconst program = new Command();\n\nprogram\n .name('json-toolkit')\n .description('Utility for working with JSON files')\n .version('1.0.0');\n\nprogram\n .command('validate')\n .argument('<file>', 'path to JSON file')\n .description('Check JSON validity')\n .action((file: string) => {\n try {\n const content = readFileSync(file, 'utf-8');\n JSON.parse(content);\n console.log(chalk.green('✓ JSON is valid!'));\n } catch (err) {\n console.error(chalk.red('✗ Error:'), (err as Error).message);\n }\n });\n\nprogram\n .command('count')\n .argument('<file>', 'path to JSON file')\n .description('Count the number of top-level keys')\n .action((file: string) => {\n if (!existsSync(file)) {\n console.error(chalk.red('File not found!'));\n return;\n }\n const content = readFileSync(file, 'utf-8');\n const data = JSON.parse(content);\n const keys = Object