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

bash
# 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/.

bash
# One-off without installing
npx ts-node ./script.ts

Output: downloads ts-node + typescript into the npm cache, then runs the script.

bash
# 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 was 10.0 (mid-2021), which standardised the ESM loader and registered the swc transformer 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 typescript as a peer (4.x or 5.x for the 10.x series).
  • SemVer respected; minor releases mostly follow typescript major 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

CompanionRole
typescriptThe actual compiler. ts-node delegates to tsc's API for type-aware emit.
@swc/core + @swc/wasmOptional transformer for faster transpilation (--swc flag); skips type-checking.
tsconfig-pathsResolves 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-devCompanion package adding fast restart-on-change. Largely superseded by tsx watch / nodemon.

Alternatives

ToolTrade-off
tsxThe 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.
bunNative TypeScript runtime — fastest, but commits you to Bun (not Node).
denoSame idea — Deno runs TS natively, separate runtime with permissions model.
swc-nodeSWC-based loader; comparable to tsx. Less polished CLI.
ts-node-devts-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.

bash
ts-node

Output:

text
>
> 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.jsonstrict, 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:

bash
ts-node --transpile-only ./script.ts

# Or via env
TS_NODE_TRANSPILE_ONLY=true ts-node ./script.ts

Output:

text
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:

json
// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": { "@/*": ["src/*"] }
  },
  "ts-node": {
    "require": ["tsconfig-paths/register"]
  }
}
bash
ts-node ./src/server.ts

Output:

text
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:

bash
ts-node-esm ./script.ts
# or
node --loader ts-node/esm ./script.ts

Output:

text
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)

diff
- "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:

  • --require hooks (e.g. dotenv/config) → use tsx's --env-file (Node 20.6+) or wrap the script entry.
  • paths aliases → add tsconfig-paths and pass --require tsconfig-paths/register.
  • experimentalDecorators → still works with tsx since esbuild supports them, but emitDecoratorMetadata requires 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:

bash
tsc -p tsconfig.json --outDir dist
node dist/server.js

Output:

text
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

bash
ts-node --transpile-only ./script.ts

Output:

text
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

bash
npm install -D @swc/core @swc/helpers

Output:

text
added 2 packages in 3s
json
// tsconfig.json
{
  "ts-node": {
    "swc": true
  }
}
bash
ts-node ./script.ts

Output:

text
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 → ToHighlights
8 → 9Node 12 dropped; default module resolution updated to match newer TS releases.
9 → 10ESM loader stabilised; ts-node-esm binary added. --esm flag introduced. Decorator metadata behaviour tightened.
10.x ongoingBundled 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

  1. Arbitrary code execution. Same as any TS runner — ts-node ./untrusted.ts runs the file with full Node privileges. Sandbox untrusted code.
  2. tsconfig.json extends chain. ts-node follows extends recursively; a malicious config in a dependency could control compile output. Pin tsconfig dependencies.
  3. @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.
  4. 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.
  5. 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

yaml
# .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:dev script. Newer NestJS templates use SWC. Migration to tsx is straightforward.
  • TypeORM CLI — depends on ts-node when running migrations against .ts migration files. The typeorm-ts-node-commonjs adapter is the current path; works without modification.
  • Mocha--require ts-node/register in .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:

bash
ts-node -r tsconfig-paths/register ./src/server.ts

Output:

text
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, then node 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