Skip to content
Node.js nd typescript 4 min read

Running TypeScript: tsx, ts-node & Native

Node.js cannot execute a .ts file on its own — the types have to be removed first. For years that meant compiling with tsc before running, but during development that round-trip is slow and clumsy. Today you have three good options for running TypeScript directly: the long-standing ts-node, the much faster tsx, and Node’s own native type stripping introduced in Node 22. This page compares them and explains when to run TypeScript directly versus compiling ahead of time for production.

How “running” TypeScript actually works

TypeScript is JavaScript with type annotations. To run it, those annotations must be erased (and any TypeScript-only syntax transformed) into plain JavaScript that the V8 engine understands. There are two broad strategies for this:

  • Type stripping — just delete the type annotations and leave everything else alone. It is extremely fast and requires no full compile, but it cannot handle constructs that emit real runtime code (like enum or namespace).
  • Full transpilation — actually compile the source, optionally type-checking it, and produce JavaScript. Slower, but complete.

The tools below sit at different points on this spectrum, trading speed for completeness and type safety.

ts-node: the original runner

ts-node registers a loader that compiles each TypeScript file on the fly using the real TypeScript compiler, so it understands every language feature and can type-check as it runs. That correctness comes at a cost: startup is noticeably slow, and it needs careful configuration to work smoothly with ES modules.

npm install --save-dev ts-node typescript
// src/index.ts
import { hostname } from "node:os";

const host: string = hostname();
console.log(`Running on ${host}`);

Run it directly. With ESM you invoke the ESM loader:

node --loader ts-node/esm src/index.ts

Output:

Running on my-laptop

By default ts-node type-checks your code, which is part of why it is slow. You can skip checking with --transpile-only (or the swc backend) to speed things up, at which point you may as well reach for tsx.

tsx: the fast modern runner

tsx (“TypeScript Execute”) uses esbuild under the hood to strip and transpile TypeScript in milliseconds. It supports both ESM and CommonJS automatically, has first-class watch mode, and needs essentially no configuration. For day-to-day development it has become the default choice.

npm install --save-dev tsx

Run a file directly, or use watch mode for an instant inner loop that restarts on every save:

npx tsx src/index.ts
npx tsx watch src/index.ts

Output:

Running on my-laptop

The important trade-off: tsx does not type-check. It strips and transpiles for speed, trusting that your editor and a separate tsc --noEmit step catch type errors. In practice this is the right division of labour — run fast, check separately.

Run type checking as its own command — tsc --noEmit — in CI and as a pre-commit hook. tsx and native stripping happily run code that has type errors, so without a dedicated check those errors reach production unnoticed.

Native type stripping in Node

Starting with Node 22.6 you can run TypeScript with no extra dependency at all using --experimental-strip-types. Node erases the type annotations and runs the result. From Node 22.7 the flag is unflagged for stripping and --experimental-transform-types adds support for enum and namespace. In Node 23.6+ and 24, stripping is on by defaultnode file.ts just works.

# Node 22.6 – 23.5
node --experimental-strip-types src/index.ts

# Node 23.6+ / 24 (enabled by default)
node src/index.ts

Output:

Running on my-laptop

Because it only strips types, the native mode rejects TypeScript-specific runtime constructs unless you also pass --experimental-transform-types. Annotation-only code — interfaces, type aliases, as casts, parameter and return types — works out of the box.

Comparison

ToolType-checks?SpeedExtra dependencyESM + CJSBest for
ts-nodeYes (default)SlowYesNeeds configCorrectness-first, legacy setups
tsxNoVery fastYesAutomaticEveryday development
Native strip (Node 22+)NoFastNoneESM + CJSZero-dependency dev, scripts
tsc then nodeYes (at build)Build-timeYes (dev only)Per configProduction builds

Dev versus production

Running TypeScript directly is a development convenience. In production you almost always want to compile ahead of time with tsc and ship the emitted JavaScript:

  • Startup is faster because there is no per-file transpilation at boot.
  • The runtime image stays slim — no tsx, ts-node, or @types/* shipped.
  • A real tsc build is the most portable artifact and runs on any Node version.

A typical package.json separates the two concerns cleanly:

{
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js",
    "typecheck": "tsc --noEmit"
  }
}

Native stripping blurs this line a little — you can run .ts directly in production on Node 22+ — but a precompiled build remains the recommended default for serious services.

Best Practices

  • Use tsx (or native stripping) for the dev loop; it gives the fastest feedback with zero or minimal setup.
  • Never rely on the runner for type safety — add a dedicated tsc --noEmit check in CI and pre-commit.
  • Prefer native stripping for quick scripts and tooling to avoid adding dependencies entirely.
  • Avoid enum and namespace if you want code to run under plain type stripping without extra flags.
  • For production, compile with tsc and run the emitted dist/ JavaScript with plain node.
  • Pin @types/node to your runtime’s major version so types match the APIs you actually run against.
Last updated June 14, 2026
Was this helpful?