tsx vs ts-node vs esno: Running TypeScript Directly in Node.js (2026)
TL;DR
tsx (TypeScript Execute) is the modern winner in 2026 — powered by esbuild, starts in milliseconds, full ESM support, replaces node drop-in, and works with node --import tsx for seamless Node.js integration. ts-node is the original TypeScript runner — slower (uses TypeScript compiler, not esbuild), but has the most complete TypeScript feature support including paths aliasing, decorators, and experimental features. esno is tsx under the hood (same esbuild-based approach, by the same author). In 2026: use tsx for development scripts and tooling; use a full build step (tsc, tsup, Vite) for production.
Key Takeaways
- tsx: ~8M weekly downloads — esbuild-powered, instant startup, ESM + CJS,
node --import tsxsyntax - ts-node: ~15M weekly downloads — TypeScript compiler, slower but full TS feature parity
- esno: ~1M weekly downloads — thin wrapper around tsx (same engine), being superseded by tsx
- tsx transpiles with esbuild, skipping type-checking — 100x faster than ts-node
- ts-node is the only option for full TypeScript diagnostics at runtime (via
typeCheck: true) - Node.js 22.6+ has experimental native TypeScript stripping — the future is built-in
The Problem: TypeScript in Scripts and Dev Tools
// Problem: Node.js doesn't natively run .ts files
// node src/migrate.ts → SyntaxError: Unexpected token ':'
// Solution: a TypeScript runner transpiles on the fly:
// tsx src/migrate.ts → runs instantly (esbuild)
// ts-node src/migrate.ts → runs (TypeScript compiler, slower)
// Use cases:
// - Database migration scripts
// - CLI tools
// - Development scripts (seeding, fixtures)
// - Testing (vitest, jest with ts-jest)
// - Build scripts that aren't worth compiling
// - Rapid prototyping
tsx
tsx — esbuild-powered TypeScript runner:
Basic usage
# Install:
npm install -D tsx
# Run a TypeScript file:
npx tsx src/index.ts
npx tsx src/scripts/migrate.ts
# Pass arguments:
npx tsx src/scripts/seed.ts --env staging
# Watch mode (re-run on file changes):
npx tsx watch src/index.ts
Drop-in replacement for node
# Use tsx as a node replacement:
tsx src/index.ts
# Works with node flags:
tsx --env-file=.env src/index.ts
node --import tsx src/index.ts # Preferred for Node.js 18+
package.json scripts
{
"scripts": {
"dev": "tsx watch src/index.ts",
"migrate": "tsx src/db/migrate.ts",
"seed": "tsx src/db/seed.ts",
"generate": "tsx scripts/generate-types.ts"
},
"devDependencies": {
"tsx": "^4.0.0"
}
}
Node.js loader mode (recommended for Node.js 18+)
# Register tsx as a loader — allows require/import of .ts files:
node --import tsx/esm src/index.ts # ESM mode
node --require tsx/cjs src/index.ts # CJS mode
# In package.json:
{
"scripts": {
"start:dev": "node --import tsx src/index.ts"
}
}
With Vitest and Jest
// vitest.config.ts — tsx handles TypeScript for vitest natively:
import { defineConfig } from "vitest/config"
export default defineConfig({
test: {
// Vitest already uses Vite/esbuild — no tsx needed for tests
},
})
// jest.config.ts — use tsx as transformer:
export default {
transform: {
"^.+\\.tsx?$": [
"ts-jest",
{ useESM: true },
],
// Or use tsx with jest directly via @jest/globals
},
}
tsconfig compatibility
// tsx reads your tsconfig.json automatically:
// compilerOptions.paths → resolved (via esbuild's tsconfig-paths)
// compilerOptions.target → respected
// compilerOptions.moduleResolution → bundler mode
// decorators → supported via esbuild
// tsx does NOT run type checking (it's a transpiler, not type checker)
// Run type checking separately:
// tsc --noEmit
What tsx does NOT support
# tsx strips types — it does NOT:
# - Report type errors
# - Support experimental features not in esbuild (const enums across files)
# - Support all tsconfig.json paths configurations equally
# For production with full tsc features, use tsc or tsup to build:
npx tsc --noEmit # Type check only (no output)
npx tsup src/index.ts # Build with full TS + bundle
ts-node
ts-node — the original TypeScript Node.js runner:
Basic usage
# Install:
npm install -D ts-node typescript
# Run:
npx ts-node src/index.ts
# REPL:
npx ts-node
tsconfig.json for ts-node
{
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"paths": {
"@/*": ["./src/*"]
}
},
"ts-node": {
"swc": true, // Use SWC for transpilation (much faster!)
"esm": false, // Enable ESM (requires Node.js 12+)
"experimentalSpecifierResolution": "node"
}
}
SWC mode (fast ts-node)
# ts-node with SWC transpiler (similar speed to tsx):
npm install -D ts-node @swc/core @swc/helpers
# In tsconfig.json:
# "ts-node": { "swc": true }
npx ts-node --swc src/index.ts
# Or with the config above, just:
npx ts-node src/index.ts
ESM mode
# ts-node ESM mode requires:
# 1. tsconfig.json: { "compilerOptions": { "module": "ESNext" } }
# 2. package.json: { "type": "module" }
npx ts-node --esm src/index.ts
# Or use node with loader:
node --loader ts-node/esm src/index.ts
Full type checking at runtime
// ts-node with type checking enabled (slow but catches type errors):
// tsconfig.json:
{
"ts-node": {
"typeCheck": true // Default: false. Enables full diagnostics.
}
}
// CLI:
// ts-node --type-check src/index.ts
// Use case: CI validation scripts where type errors should halt execution
// Not recommended for dev iteration — too slow
ts-node vs tsx performance
Cold start (simple script):
ts-node (default): ~2000ms (TypeScript compiler)
ts-node (swc: true): ~200ms (SWC transpiler)
tsx: ~100ms (esbuild)
esno: ~100ms (same as tsx)
Bun (built-in TS): ~50ms (no tool needed)
For 100-file project:
ts-node: noticeably slower on each run
tsx: still fast — esbuild is extremely efficient
esno
esno — thin esbuild-based runner:
# esno is a thin wrapper around tsx with a slightly different CLI:
npm install -D esno
# ESM mode:
npx esno src/index.ts
# CJS mode:
npx esno/esno src/index.ts # or: npx cjs-esno src/index.ts
// esno is now just an alias for tsx from the same author
// The author (antfu) recommends using tsx directly in new projects:
// "esno is essentially the same as tsx now"
// If you see esno in older projects, it can be replaced with tsx:
// npm uninstall esno && npm install -D tsx
// Replace "esno" with "tsx" in package.json scripts
Feature Comparison
| Feature | tsx | ts-node | esno |
|---|---|---|---|
| Transpiler | esbuild | TypeScript (or SWC) | esbuild |
| Cold start | ⚡ ~100ms | 🐌 ~2000ms (or ~200ms with SWC) | ⚡ ~100ms |
| Type checking | ❌ | ✅ (optional) | ❌ |
| ESM support | ✅ Full | ✅ (with config) | ✅ Full |
| CJS support | ✅ | ✅ | ✅ |
| Watch mode | ✅ | ✅ | ✅ |
| tsconfig paths | ✅ | ✅ | ✅ |
| Decorators | ✅ (esbuild) | ✅ (TS compiler) | ✅ |
const enum across files | ❌ | ✅ | ❌ |
| Bun alternative | bun run | N/A | N/A |
| Weekly downloads | ~8M | ~15M | ~1M |
When to Use Each
Choose tsx if:
- Running TypeScript scripts, CLIs, and dev tooling (the default choice in 2026)
- Need fast startup for scripts that run frequently
- Full ESM support with
node --import tsx - Drizzle migrations, Prisma seed files, custom build scripts
Choose ts-node if:
- You need runtime type checking (
typeCheck: true) - Using TypeScript features esbuild doesn't fully support (complex
const enumacross files) - Legacy project already on ts-node — no reason to migrate unless performance is a problem
- Need maximum tsconfig.json compatibility
Choose esno if:
- Inherited a project using esno — it works fine, consider migrating to tsx
Use Bun instead if:
- Already running Bun — it runs TypeScript natively with zero configuration (
bun run src/index.ts)
For production builds — don't use any of these:
# Always build for production with a real compiler:
npx tsc --outDir dist # Full TypeScript compiler
npx tsup src/index.ts # esbuild with TypeScript, bundles + dts
npx tsdown src/index.ts # Rolldown-based, faster tsup alternative
Methodology
Download data from npm registry (weekly average, February 2026). Feature comparison based on tsx v4.x, ts-node v10.x, and esno v0.x.
Compare TypeScript tooling and developer experience packages on PkgPulse →