- Published on
Running TypeScript Directly in 2026 — tsx, ts-node, and Native Node.js
- Authors

- Name
- Sanjeev Sharma
- @webcoderspeed1
Introduction
Five years ago, running TypeScript required compilation. Today, three approaches compete: tsx for speed, ts-node for compatibility, and native Node.js --experimental-strip-types for zero dependencies. This post benchmarks all three and teaches you to pick the right tool.
tsx vs ts-node Performance Comparison
Real startup time comparison on a MacBook Pro:
# Original TypeScript file
$ wc -l src/index.ts
150 lines
# Using ts-node
$ time npx ts-node src/index.ts
real 0m0.834s
# Using tsx
$ time npx tsx src/index.ts
real 0m0.215s
# Ratio: tsx is 3.8x faster
For larger projects (1000+ lines):
| Tool | Startup Time | Compile Time |
|---|---|---|
| ts-node | 1.2s | 800ms |
| tsx | 0.25s | 150ms |
| Node + swc | 0.18s | 120ms |
| Node --strip-types | 0.08s | 0ms |
tsx is fastest among transpilers; native stripping has no overhead.
tsx watch for Development
Development mode with file watching:
# Watch and restart on changes
tsx watch src/server.ts
# With custom args
tsx watch --node-arg=--experimental-permission src/server.ts
# Hot reload with tsx:
tsx watch --clear-screen src/index.ts
tsx watch is faster than ts-node --watch and includes automatic restart:
// src/server.ts
import express from 'express';
const app = express();
app.get('/', (req, res) => {
res.json({ message: 'Hello from tsx watch' });
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
// Save this file → tsx automatically restarts
tsx --import as Loader
Use tsx as a loader for other Node.js CLIs:
# Run Drizzle migrations with tsx
node --loader tsx/cjs ./node_modules/drizzle-kit/bin.cjs migrate
# Run jest with tsx
node --loader tsx/cjs ./node_modules/jest/bin/jest.js
# Or use tsx as wrapper
tsx ./node_modules/drizzle-kit/bin.cjs migrate
In package.json:
{
"scripts": {
"migrate": "tsx ./node_modules/drizzle-kit/bin.cjs migrate",
"test": "tsx ./node_modules/jest/bin/jest.js",
"build": "tsx ./scripts/build.ts"
}
}
Node 22 --experimental-strip-types (No Transpile)
Node.js 22.5+ can strip TypeScript syntax directly without transpilation:
# No transpilation, just syntax stripping
node --experimental-strip-types src/index.ts
# With shebang for CLI tools
#!/usr/bin/env node --experimental-strip-types
# Performance: fastest startup (0.08s for small files)
time node --experimental-strip-types src/index.ts
Important caveat: --experimental-strip-types only works if you don't use:
- Decorators (still require transpilation)
consttype parameters (need transpilation)- Type-only imports with side effects
- Enums (enums add runtime code)
Valid syntax for native stripping:
// ✓ Works with --experimental-strip-types
interface User {
id: number;
name: string;
}
type Status = 'active' | 'inactive';
const user: User = { id: 1, name: 'Alice' };
const status: Status = 'active';
function greet(name: string): void {
console.log(`Hello, ${name}`);
}
// ✓ Type-only imports
import type { Response } from 'express';
// ✗ These require transpilation
@deprecated // Decorators
class OldClass { }
enum Color { // Enums have runtime code
Red = 0,
Blue = 1,
}
For simple backend services with no decorators or enums, --experimental-strip-types is the winner.
- tsx vs ts-node Performance Comparison
- tsx watch for Development
- tsx --import as Loader
- Node 22 --experimental-strip-types (No Transpile)
- When Native Stripping is Enough
- ts-node --esm for Pure ESM TypeScript
- Production: Compile First vs Direct Run
- Bun's Native TS Support
- Deno's Native TS
- Choosing the Right Tool for Different Project Types
- Checklist
- Conclusion
When Native Stripping is Enough
Use --experimental-strip-types when:
- No decorators
- No enum definitions
- No
consttype parameters - Simple, straightforward TypeScript
// Perfect for --experimental-strip-types
import { readFileSync, writeFileSync } from 'fs';
import { parse } from 'csv-parse/sync';
interface Row {
id: number;
email: string;
name: string;
}
function parseCSV(content: string): Row[] {
const records = parse(content, {
columns: true,
skip_empty_lines: true,
});
return records as Row[];
}
async function main(): Promise<void> {
const content = readFileSync('data.csv', 'utf-8');
const rows = parseCSV(content);
console.log(`Parsed ${rows.length} rows`);
}
main().catch(console.error);
Run with:
node --experimental-strip-types src/csv-parser.ts
Zero transpilation overhead.
ts-node --esm for Pure ESM TypeScript
If you need ts-node with ESM:
# ESM mode
node --loader ts-node/esm src/index.ts
# Or in package.json
{
"type": "module",
"scripts": {
"dev": "node --loader ts-node/esm src/index.ts"
}
}
ts-node ESM setup is slower but works with complex TypeScript.
Production: Compile First vs Direct Run
Compile First (recommended):
# Build at deployment time
npm run build
node dist/server.js
Benefits:
- Fast startup (no transpilation overhead)
- Detect type errors before running
- Works with all TypeScript features
- Standard deployment pattern
// package.json
{
"scripts": {
"build": "tsc",
"start": "node dist/server.js",
"dev": "tsx watch src/server.ts"
}
}
Direct Run (development only):
# For development and small services
tsx src/server.ts
Benefits:
- Skip build step
- Faster iteration
- Simpler deployment (no dist/ directory)
Trade-offs:
- Slower startup (but acceptable for small services <500 lines)
- Type errors caught at runtime
Hybrid (best practice):
{
"scripts": {
"dev": "tsx watch src/server.ts",
"build": "tsc",
"start": "node dist/server.js",
"start:dev": "tsx src/server.ts"
}
}
For production: compile with tsc. For development: use tsx watch.
Bun's Native TS Support
Bun runs TypeScript natively without tsx or ts-node:
# Just works
bun src/index.ts
# Watch mode
bun --watch src/index.ts
Bun is a faster alternative but requires migration from Node.js:
- Different API surface (
Bun.file(),Bun.serve()) - Incompatible with Node.js ecosystem (gradually improving)
- Excellent for new projects, risky for existing backends
// Bun native
const server = Bun.serve({
port: 3000,
fetch(request: Request) {
return new Response('Hello from Bun!');
},
});
console.log(`Listening on ${server.url}`);
Bun is production-ready (1.0+) but Node.js ecosystem is larger.
Deno's Native TS
Deno also runs TypeScript natively:
deno run src/index.ts
Deno is modern but incompatible with Node.js:
- No
node_modules(URL imports) - No npm packages (NPM package support coming)
- Different permissions model
- Smaller ecosystem
For backend APIs, Node.js + tsx is more practical than Deno.
Choosing the Right Tool for Different Project Types
| Project | Recommended Tool | Reason |
|---|---|---|
| API microservice (simple) | Node --experimental-strip-types | Fastest |
| API microservice (complex) | tsx (dev) + tsc (prod) | Type safety |
| CLI tool | tsx | Fast startup, no build |
| Cron job | tsx | Speed and simplicity |
| Lambda/Edge Function | Compile first (tsc) | Startup time critical |
| Full-stack monorepo | tsx watch (dev) + tsc (prod) | Best developer experience |
| Embedded script | tsx | Zero setup |
Decision tree:
1. Is it a production service?
✓ Yes: Compile with tsc first
✗ No: Use tsx for development
2. Does your code use decorators or enums?
✓ Yes: Use tsx or ts-node (need transpilation)
✗ No: Use --experimental-strip-types (fastest)
3. Do you need fast iteration?
✓ Yes: Use tsx watch
✗ No: Build once with tsc
Checklist
- For development: use
tsx watchorts-node --watch - For production: compile with
tsc, run withnode dist/server.js - For CLI tools: use
tsx - For simple code: try
node --experimental-strip-types - Benchmark your specific project (startup time varies)
- Use
tsxloader for npm scripts (migrations, tests) - Document dev vs prod startup in README
- Disable type checking in dev for faster iteration (optional)
- Use
esbuildfor faster builds thantsc(optional) - Consider Bun only if fully migrating away from Node.js
Conclusion
In 2026, you have multiple paths to run TypeScript. For development, tsx watch is fastest and most convenient. For production, compile with tsc to catch errors early and minimize startup time. For simple projects with no decorators, native --experimental-strip-types offers zero-overhead TypeScript. Choose based on your project's complexity and performance requirements.