cheat sheet
ts-node
Package-level reference for ts-node on npm — TypeScript-aware REPL, transpile-only mode, ESM loader, and its tsx-driven decline.
ts-node
What it is
ts-node is the original "TypeScript Execute" tool for Node.js — a CLI and Node loader that compiles .ts files on-the-fly via the TypeScript compiler API, then hands them to Node. From ~2015 until ~2023 it was the default way to run TypeScript scripts and dev servers without a build step.
It is now in maintenance mode. New projects almost always pick tsx (faster, native ESM, better defaults), and Node 22+ ships native TypeScript stripping behind a flag. ts-node survives in long-running codebases, in tooling that needs the real TypeScript compiler (for paths alias resolution, decorators metadata, project references), and in the REPL niche where its TS-aware shell is still unmatched.
Install
# Project-local with the TS compiler
npm install -D ts-node typescript
pnpm add -D ts-node typescript
yarn add -D ts-node typescript
Output: ts-node, ts-node-esm, ts-node-script, and ts-node-transpile-only binaries under node_modules/.bin/.
# One-off without installing
npx ts-node ./script.ts
Output: downloads ts-node + typescript into the npm cache, then runs the script.
# Global (not recommended; pins typescript at user level)
npm install -g ts-node typescript
Output: ts-node and typescript available system-wide.
Versioning & Node support
- Current line is
10.x. The previous big break was10.0(mid-2021), which standardised the ESM loader and registered theswctransformer option. - Requires Node 14.x or newer;
10.9+works on Node 18 / 20 / 22 (though native TS stripping in 22 makes ts-node redundant for the simple case). - Always paired with
typescriptas a peer (4.x or 5.x for the10.xseries). - SemVer respected; minor releases mostly follow
typescriptmajor bumps. - Maintenance has slowed — the repo still merges PRs but rarely ships new features. Watch the changelog rather than the npm version page.
Package metadata
- Maintainers: TypeStrong (Andrew Bradley et al.)
- Project home: github.com/TypeStrong/ts-node
- Docs: typestrong.org/ts-node
- npm: npmjs.com/package/ts-node
- License: MIT
- First released: 2015
- Downloads: still ~20M+ weekly — install base is enormous even as new projects move off it.
Peer dependencies & extras
| Companion | Role |
|---|---|
typescript | The actual compiler. ts-node delegates to tsc's API for type-aware emit. |
@swc/core + @swc/wasm | Optional transformer for faster transpilation (--swc flag); skips type-checking. |
tsconfig-paths | Resolves paths aliases at runtime — frequently needed; not bundled. |
@types/node | @types/node only; ts-node doesn't depend on @types/* but most consumers do. |
ts-node-dev | Companion package adding fast restart-on-change. Largely superseded by tsx watch / nodemon. |
Alternatives
| Tool | Trade-off |
|---|---|
| tsx | The recommended successor — esbuild-based, ESM by default, watch mode built-in. 10–100× faster startup. Strips types without checking. |
| node --import (Node 22+) | Native TypeScript stripping via --experimental-strip-types. No JSX, no decorators, no paths. Smallest dependency footprint. |
| bun | Native TypeScript runtime — fastest, but commits you to Bun (not Node). |
| deno | Same idea — Deno runs TS natively, separate runtime with permissions model. |
| swc-node | SWC-based loader; comparable to tsx. Less polished CLI. |
| ts-node-dev | ts-node + auto-restart. Use tsx watch instead in new projects. |
Real-world recipes
The remaining places ts-node still earns its keep — plus migration hints when it doesn't.
TypeScript-aware REPL
ts-node's REPL is still the best interactive TS shell. tsx and Node's native runner don't (yet) match its inline type inference.
ts-node
Output:
>
> const x: number = 42
undefined
> x.toFixed(2)
'42.00'
> interface Pt { x: number; y: number }
undefined
> const p: Pt = { x: 1, y: 2 }
undefined
> p.x + p.y
3
The REPL respects your local tsconfig.json — strict, paths, lib all apply. Great for spelunking through a codebase or sketching types.
--transpile-only for fast scripts
When you don't need type-checking at runtime (you have tsc --noEmit in CI), skip it for ~5× faster startup:
ts-node --transpile-only ./script.ts
# Or via env
TS_NODE_TRANSPILE_ONLY=true ts-node ./script.ts
Output:
Hello, world (transpile-only — types were not checked)
This is the mode tsx operates in by default; ts-node --transpile-only is the closest equivalent without leaving the ts-node ecosystem.
Project references and paths aliases
ts-node integrates with the TypeScript compiler API, so paths, references, and composite projects work without extra packages:
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": { "@/*": ["src/*"] }
},
"ts-node": {
"require": ["tsconfig-paths/register"]
}
}
ts-node ./src/server.ts
Output:
Resolved @/utils → src/utils — server listening on :3000
tsx requires an explicit tsconfig-paths/register (or tsx-paths) layer; ts-node bakes it into its config block.
ESM mode
For ESM packages ("type": "module"), use the ts-node-esm binary or pass --esm:
ts-node-esm ./script.ts
# or
node --loader ts-node/esm ./script.ts
Output:
ExperimentalWarning: --experimental-loader may be removed in the future...
top-level await ran successfully
The experimental-loader warning is harmless. New projects should prefer Node's --import style or move to tsx.
Migration to tsx (the common case)
- "dev": "ts-node-esm src/server.ts",
- "migrate": "ts-node --transpile-only ./scripts/migrate.ts",
+ "dev": "tsx watch src/server.ts",
+ "migrate": "tsx ./scripts/migrate.ts",
Verify with npm run dev after replacement. Things that need extra care:
--requirehooks (e.g.dotenv/config) → usetsx's--env-file(Node 20.6+) or wrap the script entry.pathsaliases → addtsconfig-pathsand pass--require tsconfig-paths/register.experimentalDecorators→ still works withtsxsince esbuild supports them, butemitDecoratorMetadatarequires the TypeScript compiler — stay on ts-node for legacy NestJS / TypeORM.
Production deployment
Same rule as tsx: don't. Build to JavaScript with tsc and ship the JS:
tsc -p tsconfig.json --outDir dist
node dist/server.js
Output:
Server listening on :3000
The legacy exception is NestJS, TypeORM, and other decorator-metadata-heavy frameworks where tsc's emitDecoratorMetadata is required and people occasionally ship ts-node-running containers in dev/staging. Even there, the right answer is "build with tsc, ship dist/".
Performance tuning
ts-node is the slow path. Optimisations:
Use --transpile-only
ts-node --transpile-only ./script.ts
Output:
hello from script.ts
Skips the type-checker entirely — ~5× faster cold start. Pair with tsc --noEmit in a pre-commit hook so type errors don't disappear.
Swap to SWC
npm install -D @swc/core @swc/helpers
Output:
added 2 packages in 3s
// tsconfig.json
{
"ts-node": {
"swc": true
}
}
ts-node ./script.ts
Output:
Hello (transpiled by SWC, ~3× faster than tsc)
--swc skips type-checking and uses SWC's Rust transpiler. Effectively turns ts-node into a slower tsx.
Disable diagnostics in CI
TS_NODE_LOG_ERROR=true and TS_NODE_PRETTY=false reduce stdout overhead in CI logs. TS_NODE_IGNORE_DIAGNOSTICS=2304,2307 suppresses specific error codes that are intentional (e.g. when stubbing imports).
Version migration guide
| From → To | Highlights |
|---|---|
| 8 → 9 | Node 12 dropped; default module resolution updated to match newer TS releases. |
| 9 → 10 | ESM loader stabilised; ts-node-esm binary added. --esm flag introduced. Decorator metadata behaviour tightened. |
| 10.x ongoing | Bundled TS version range expanded to 4.x and 5.x; --swc and --transpile-only got first-class config. Maintenance mode — no breaking changes expected. |
There has been talk of an 11.x line aligned with Node 22's native TS stripping, but no public roadmap commits to it as of writing.
Security considerations
- Arbitrary code execution. Same as any TS runner —
ts-node ./untrusted.tsruns the file with full Node privileges. Sandbox untrusted code. tsconfig.jsonextendschain. ts-node followsextendsrecursively; a malicious config in a dependency could control compile output. Pin tsconfig dependencies.@types/*typosquatting. ts-node doesn't audit@types/*packages; they're code (declaration files), and a malicious one could declare types that mask runtime bugs. Pin via lockfile.experimentalDecorators+emitDecoratorMetadata. Decorator metadata embeds type names in compiled output — those can leak source structure to anyone with access to compiled JS or stack traces.- Loader hook ordering. When chaining loaders (
--loader ts-node/esm --loader @cspotcode/source-map-support), order matters. Register source-map handlers BEFORE ts-node so stack-frame rewriting sees the original locations.
Testing & CI integration
# .github/workflows/ci.yml — legacy ts-node setup
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx tsc --noEmit
- run: npx ts-node --transpile-only ./scripts/seed.ts
- run: npm test
Most newer projects replace npx ts-node with npx tsx in CI scripts — same surface, faster.
Jest integration uses ts-jest (its own transformer, not ts-node); Mocha integration uses --require ts-node/register in .mocharc.json. New projects should prefer vitest, which transpiles natively.
Ecosystem integrations
- NestJS — historically shipped with a ts-node-based
start:devscript. Newer NestJS templates use SWC. Migration totsxis straightforward. - TypeORM CLI — depends on ts-node when running migrations against
.tsmigration files. Thetypeorm-ts-node-commonjsadapter is the current path; works without modification. - Mocha —
--require ts-node/registerin.mocharc.json. Vitest is the modern alternative. - Sequelize CLI / Strapi — both still ship ts-node-based dev modes; their respective issue trackers track tsx migration.
- VS Code launch configs —
"runtimeArgs": ["-r", "ts-node/register"]is still the canonical "debug TypeScript" snippet.
Troubleshooting common errors
Error [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts"
ESM mode requires ts-node-esm or --loader ts-node/esm. Default ts-node only handles CJS.
TS2307: Cannot find module '@/utils'
paths aliases need tsconfig-paths/register loaded before the script:
ts-node -r tsconfig-paths/register ./src/server.ts
Output:
Server listening on http://localhost:3000
SyntaxError: Cannot use import statement outside a module
Same as ESM-vs-CJS confusion above. Add "type": "module" or rename to .mts, and use ts-node-esm.
Could not find a declaration file for module 'foo'
ts-node respects strict: true. Either install @types/foo, add declare module 'foo' in a .d.ts, or pass --transpile-only to skip the check.
REPL prints "experimental warning" stack traces
The ESM loader is experimental; the warning is benign. Suppress with NODE_OPTIONS='--no-warnings' ts-node-esm.
When NOT to use this
- New projects. Pick
tsx. ts-node is in maintenance, not active development. Migration costs are low. - Simple type-stripping needs on Node 22+. Use
node --experimental-strip-types— zero dependencies, native speed. - Production servers. Build to JS with
tsc, thennode dist/. Runtime transpile is wasted CPU. - Edge / serverless environments. Cold-start cost (loading the entire TS compiler) is enormous. Build at deploy time.
- Decorator-free codebases. The main reason to stay on ts-node is
emitDecoratorMetadata— without that, tsx is strictly better.
See also
- JavaScript: ts-node — CLI commands, REPL usage, flags
- JavaScript: typescript — TypeScript language fundamentals
- Packages: npm-tsx — the recommended successor
- Concept: async — ESM top-level await in scripts