tsx vs tsc vs ts-node
Pick the right TypeScript toolchain per environment - tsx for fast dev and tests, tsc for production dist/, and avoid new ts-node adoption unless brownfield constraints require it.
Recipe
{
"scripts": {
"dev": "node --watch --import tsx src/main.ts",
"build": "tsc -p tsconfig.json",
"start": "node dist/main.js",
"test": "node --import tsx --test src/**/*.test.ts"
},
"devDependencies": {
"typescript": "^5.6.0",
"tsx": "^4.0.0",
"@types/node": "^24.0.0"
}
}When to reach for this:
- Bootstrapping a new Express 5 or Fastify 5 service
- Choosing CI commands that mirror production
- Replacing
ts-node-devornodemon+ts-nodestacks - Debugging "works with tsx, fails with node dist" mismatches
Working Example
// src/main.ts
import { createServer } from 'node:http';
const port = Number(process.env.PORT ?? 3000);
createServer((_req, res) => {
res.end('ok');
}).listen(port, () => console.log(`Listening ${port}`));# Development - no separate compile step
npm run dev
# Production pipeline
npm run build
npm run start// tsconfig.json - emit for production
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "dist",
"rootDir": "src",
"declaration": true,
"sourceMap": true,
"strict": true
}
}What this demonstrates:
tsxhooks Node's loader - transforms TS on the fly via esbuildtscwritesdist/main.jspreserving ESM import specifiers with.jsextensions- Production runs plain
node- no devDependency loader in the container --watch+tsxreplaces nodemon for many services
Deep Dive
How It Works
- tsx - esbuild transform, fast startup, supports ESM and
import.meta, ideal for dev/test. - tsc - full typecheck + emit; slower but authoritative for declarations and
dist/. - ts-node - older on-the-fly compiler integration; ESM support more finicky than tsx.
- tsc --noEmit - typecheck-only CI job without emit - pairs with
buildjob.
Pipeline Comparison
| Tool | Typecheck | Emit | Prod runtime | Speed |
|---|---|---|---|---|
| tsx | No (transpile only) | No | Dev/test only | Fast |
| tsc | Yes | Yes | Yes (dist/) | Moderate |
| ts-node | Partial | No | Legacy dev | Moderate |
| tsc + tsx | Split CI jobs | tsc | node dist | Best practice |
TypeScript Notes
{
"scripts": {
"typecheck": "tsc --noEmit",
"dev": "node --watch --import tsx src/main.ts"
}
}Run typecheck in CI even when dev uses tsx - tsx alone skips type errors.
Gotchas
- Shipping tsx to production - adds devDependency surface and transform overhead. Fix:
node dist/main.jsin DockerCMD. - tsx passes type errors - transpile-only. Fix:
tsc --noEmitin CI gate. - Path aliases without emit support -
tscdoes not rewritepaths. Fix: NodeNext relative imports ortsc-aliaspost-step. - Different module output (CJS vs ESM) - tsx runs source;
tscemits permodule. Fix: alignpackage.json"type"with emit. - Keeping ts-node for ESM in 2026 - harder config than tsx. Fix: migrate dev scripts to
node --import tsx.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
SWC (@swc-node) | NestJS 11 SWC pipeline | Simple service needing minimal config |
| esbuild bundle | Single-file Lambda artifact | Multi-file Node server with dynamic import |
tsc --build project references | Large monorepos | Single-package API |
| Plain JS (no TS) | Tiny scripts | Teams standardized on TypeScript |
FAQs
Is tsx production-ready?
For running TS in prod - avoid. For dev/test - yes. Production should run compiled JS.
Do I still need tsc with tsx?
Yes - for typechecking and emitting dist/. tsx is not a typechecker.
What replaces ts-node-dev?
node --watch --import tsx src/main.ts on Node 24.
Can tsx run node:test?
Yes: node --import tsx --test src/**/*.test.ts.
Should Docker run tsc in build stage?
Yes - multi-stage: npm ci, npm run build, copy dist/ + prod deps to runtime image.
Does NestJS 11 use tsc or SWC?
Both supported - SWC faster compile; tsc stricter default for smaller services.
What about tsx and env files?
node --env-file=.env --import tsx src/main.ts loads env before TS executes.
Can I debug tsx with inspect?
node --inspect --import tsx src/main.ts - breakpoints map via source maps if enabled.
Why does dist import .js but src imports .js too?
NodeNext requires .js specifiers in source so emit matches runtime.
Is ts-node deprecated?
Not officially, but new Node 24 projects should standardize on tsx for on-the-fly ESM.
How do I speed up tsc?
skipLibCheck, project references, and incremental "incremental": true with .tsbuildinfo.
What runs first in CI?
npm run typecheck then npm run test then npm run build - fail fast on types.
Related
- TypeScript in Node Basics - tsconfig defaults
- Gradual Typing Brownfield - migrating JS codebases
- Typecheck in CI - gate merges
- Running Scripts & Shebang - npm scripts
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.