Skip to main content

tsx for Development

You've built a CLI with Commander.js and added interactive features with ora and chalk. But there's friction in your development loop: every code change requires stopping the process, recompiling TypeScript, and running again. That compile step adds up to hundreds of interruptions per day.

What if you could run TypeScript directly, like Python? What if your CLI script could execute immediately when you type its name, without node or npx? What if file changes triggered automatic re-execution?

This is what tsx provides: zero-config TypeScript execution that eliminates the build step from development. Your iteration loop drops from "edit, compile, run" to just "edit, run." When building CLI tools, this acceleration compounds into hours saved per week.

The Problem with Traditional TypeScript Development

The standard TypeScript workflow creates friction:

# Traditional workflow (3 steps per iteration)
tsc src/cli.ts --outDir dist # Step 1: Compile
node dist/cli.js chat "Hello" # Step 2: Run
# ... see error, edit file ...
tsc src/cli.ts --outDir dist # Step 3: Recompile
node dist/cli.js chat "Hello" # Step 4: Run again

Output:

src/cli.ts(15,5): error TS2345: Argument of type 'string' is not assignable...

Every small change requires recompilation. For CLI development where you're constantly testing command variations, this adds significant overhead.

ts-node improved this by running TypeScript directly:

npx ts-node src/cli.ts chat "Hello"

But ts-node has drawbacks: slower startup (it type-checks before running), complex configuration for ESM modules, and compatibility issues with modern Node.js features.

tsx: Zero-Config TypeScript Execution

tsx is a faster alternative that uses esbuild for near-instant TypeScript transformation:

# Install tsx
npm install -D tsx

# Run TypeScript directly
npx tsx src/cli.ts chat "Hello"

Output:

Thinking...
Hello! How can I help you today?

No compilation step. No configuration. Just run.

Speed Comparison

ToolCold StartHot StartType Checking
ts-node~800ms~400msYes (slower)
tsx~100ms~50msNo (faster)
Compiled JS~50ms~30msPre-compiled

tsx achieves near-native speed by skipping type checking at runtime. Your IDE handles type checking continuously; tsx focuses on execution speed.

Basic Usage Patterns

# Run a single file
npx tsx src/cli.ts

# Pass arguments
npx tsx src/cli.ts chat "Explain async/await"

# Run with environment variables
API_KEY=sk-xxx npx tsx src/cli.ts config show

Output:

# Each command executes immediately without compilation

ESM and CommonJS Support

tsx handles both module systems automatically:

// Works with ESM imports
import { Command } from "commander";
import chalk from "chalk";

// Works with CommonJS requires
const fs = require("fs");

Output:

# Both import styles work without configuration

No "type": "module" gymnastics. No .mjs extensions. tsx figures out what you need.

Shebang Scripts: Executable TypeScript

A shebang (pronounced "sha-bang") makes scripts directly executable. Instead of npx tsx script.ts, you type ./script.ts or just script after adding it to your PATH.

Creating a Shebang Script

Add this line as the very first line of your TypeScript file:

#!/usr/bin/env tsx
import { Command } from "commander";

const program = new Command();

program
.name("ai-chat")
.description("Chat with AI from your terminal")
.argument("<prompt>", "Your message to the AI")
.action(async (prompt: string) => {
console.log(`You said: ${prompt}`);
// AI logic here
});

program.parse();

Output:

# This file can now be executed directly

Making It Executable

# Make the script executable
chmod +x src/cli.ts

# Run directly (no npx needed)
./src/cli.ts "Hello, AI!"

Output:

You said: Hello, AI!

The shebang #!/usr/bin/env tsx tells the operating system to use tsx as the interpreter. /usr/bin/env finds tsx in your PATH, making the script portable across different installations.

Shebang Syntax Breakdown

PartPurpose
#!Shebang marker (tells OS this is an interpreted script)
/usr/bin/envUtility that finds commands in PATH
tsxThe interpreter to use

This pattern works because tsx is installed globally or in your project's node_modules/.bin.

Common Shebang Patterns

#!/usr/bin/env tsx
// Standard: uses tsx from PATH

#!/usr/bin/env -S npx tsx
// Alternative: explicitly use npx (works if tsx isn't globally installed)

#!/usr/bin/env node
// For compiled JavaScript (not TypeScript)

Output:

# Different shebangs for different deployment scenarios

Watch Mode for Rapid Iteration

Watch mode automatically re-runs your script when files change. This is invaluable for CLI development.

Basic Watch Mode

# Watch a single file
npx tsx watch src/cli.ts chat "Test message"

Output:

Thinking...
Hello! I received your test message.

[watch] Rerunning...
Thinking...
Updated response after your code change!

Edit src/cli.ts, save, and tsx immediately re-executes with your changes. No manual restart needed.

Watch with Multiple Arguments

# Watch while passing complex arguments
npx tsx watch src/cli.ts chat --model gpt-4 --stream "Explain monads"

Output:

[watch] Watching for file changes...
Streaming response: Monads are a design pattern...

[watch] File changed: src/cli.ts
[watch] Rerunning...
Streaming response: Let me explain monads differently...

Effective Watch Mode Workflow

A typical development session:

# Terminal 1: Watch mode running
npx tsx watch src/cli.ts chat "Test prompt"

# Terminal 2: Edit files
# Every save triggers re-execution in Terminal 1

This workflow mirrors Python development: edit, save, see results. No mental context switch for "now I need to recompile."

Watch Mode vs nodemon

You might know nodemon for watching Node.js files. tsx watch is simpler for TypeScript:

Featurenodemon + ts-nodetsx watch
SetupRequires configurationZero config
SpeedSlower (type checking)Fast (esbuild)
ESM supportComplex setupAutomatic

Before publishing to npm, you want to test your CLI as if it were globally installed. npm link creates a symlink from your development directory to your global npm folder.

Setting Up package.json

First, configure your CLI's entry point:

{
"name": "@yourname/ai-chat",
"version": "1.0.0",
"bin": {
"ai-chat": "./src/cli.ts"
},
"type": "module"
}

Output:

// The "bin" field maps command names to script files
# In your project directory
npm link

# Now use your CLI from anywhere
ai-chat "Hello from anywhere!"

Output:

added 1 package, and audited 2 packages in 1s
You said: Hello from anywhere!

npm link does two things:

  1. Creates a symlink in your global npm folder pointing to your project
  2. Makes the command available in your terminal's PATH
# Step 1: Link your package globally
cd ~/projects/ai-chat-cli
npm link

# Step 2: Use it from any directory
cd ~/Documents
ai-chat "Works from here!"

cd /tmp
ai-chat "Works from here too!"

Output:

Works from here!
Works from here too!

Unlinking When Done

# Remove the global link
npm unlink -g @yourname/ai-chat

Output:

removed 1 package in 0.5s

Combine npm link with watch mode for the complete CLI development workflow:

# Terminal 1: Link and watch
npm link
npx tsx watch src/cli.ts --help

# Terminal 2: Test the global command
ai-chat chat "Test message"
# Edit src/cli.ts, watch restarts, test again immediately

Changes in your source code reflect immediately in the globally-linked command. No need to rebuild or re-link.

tsx vs ts-node: When to Choose Each

Both tools run TypeScript directly, but they optimize for different scenarios.

Choose tsx When:

ScenarioWhy tsx
CLI developmentFastest iteration cycle
Scripts and automationZero config, just works
ESM projectsHandles module systems automatically
Development onlyType checking happens in IDE

Choose ts-node When:

ScenarioWhy ts-node
Production executionMore battle-tested
Strict type checkingCatches errors before execution
Complex TypeScript configMore configuration options
Debugging with source mapsBetter debugger integration

Practical Decision Guide

// For this chapter's CLI development: tsx
npx tsx src/cli.ts chat "Hello"

// For production deployment: compile to JavaScript
npm run build # tsc compiles to dist/
node dist/cli.js chat "Hello"

Output:

# tsx for development speed
# Compiled JS for production reliability

Your package.json reflects this dual approach:

{
"scripts": {
"dev": "tsx watch src/cli.ts",
"build": "tsc",
"start": "node dist/cli.js"
}
}

Putting It Together: Development Workflow

Here's the complete tsx-powered CLI development workflow:

Initial Setup

# Create project
mkdir ai-chat-cli && cd ai-chat-cli
npm init -y
npm install -D tsx typescript @types/node
npm install commander ora chalk

# Create CLI entry point
touch src/cli.ts
chmod +x src/cli.ts

CLI Skeleton

#!/usr/bin/env tsx
import { Command } from "commander";
import ora from "ora";
import chalk from "chalk";

const program = new Command();

program
.name("ai-chat")
.description("AI chat from your terminal")
.version("1.0.0");

program
.command("chat")
.description("Send a message to AI")
.argument("<prompt>", "Your message")
.option("-m, --model <model>", "Model to use", "gpt-4")
.option("-s, --stream", "Stream response", false)
.action(async (prompt: string, options) => {
const spinner = ora("Thinking...").start();

// Simulate AI response
await new Promise(r => setTimeout(r, 1000));
spinner.stop();

console.log(chalk.green("AI: ") + `You asked about: ${prompt}`);
console.log(chalk.dim(`Model: ${options.model}, Stream: ${options.stream}`));
});

program.parse();

Output:

./src/cli.ts chat "Hello" --model claude-3 --stream
# Thinking... (spinner)
# AI: You asked about: Hello
# Model: claude-3, Stream: true

Development Loop

# Link for global access
npm link

# Watch for changes
npx tsx watch src/cli.ts chat "Test"

# Make changes, watch auto-restarts
# Test from another terminal: ai-chat chat "Hello"

Verify Everything Works

# Direct execution
npx tsx src/cli.ts chat "Direct test"

# Shebang execution
./src/cli.ts chat "Shebang test"

# Global command (after npm link)
ai-chat chat "Global test"

Output:

AI: You asked about: Direct test
AI: You asked about: Shebang test
AI: You asked about: Global test

Try With AI

Prompt 1: Set Up Your tsx Environment

I'm building a CLI tool in TypeScript. Help me set up tsx for development.
I need:
1. A package.json with tsx as a dev dependency
2. Scripts for dev (watch mode), build (compile), and start (run compiled)
3. A basic CLI entry point with Commander.js

Show me the files I need and explain each script's purpose.

What you're learning: How to configure a TypeScript project with tsx for development and tsc for production. The separation between development tools (fast, no type check) and production builds (compiled, optimized) is a pattern you'll use throughout your AI engineering career.

Prompt 2: Debug a Shebang Issue

My TypeScript CLI with shebang isn't working. When I run ./src/cli.ts, I get:
"command not found: tsx"

But npx tsx src/cli.ts works fine.

My shebang line is: #!/usr/bin/env tsx

Help me debug this. What are the possible causes and how do I fix each one?

What you're learning: Troubleshooting PATH issues and understanding how shells find executables. This debugging skill transfers to any command-line tool development, not just TypeScript CLIs.

Prompt 3: Compare Your Options

I'm confused about when to use different TypeScript execution methods.
Compare these for me:

1. npx tsx src/cli.ts
2. npx ts-node src/cli.ts
3. tsc && node dist/cli.js
4. Using shebang with #!/usr/bin/env tsx

For each one, tell me:
- Speed (startup time)
- When to use it
- Downsides or gotchas

I'm building a CLI that I'll eventually publish to npm. What's my best workflow?

What you're learning: Tool selection based on requirements. Production engineering requires choosing the right tool for each phase of development. This decision-making process applies to all technology choices in AI system development.

Safety note: When using npm link, remember it creates a global symlink that affects your entire system. Always unlink (npm unlink -g packagename) when you're done testing to avoid confusion with other projects. If something stops working unexpectedly, check if you have orphaned global links from previous projects.