Skip to content

Node.js 23 Native TypeScript Support Without Other

Node.js 23.6 ships --experimental-strip-types to run .ts files directly. No ts-node, no build step. Learn what works and what still needs tsc.

· · 7 min read
A Node.js backend server terminal showing TypeScript files running directly without a build step

Quick Take

For years, running TypeScript in Node.js meant picking a tool: ts-node, tsx, Bun, or Deno. Node.js 23.6 changed that. The --experimental-strip-types flag lets the runtime execute .ts files directly by stripping type annotations before execution. No transpilation, no sourcemaps, no config, just node.

Pair this with a strict TypeScript config (see TypeScript strict mode), --experimental-strip-types removes the runtime type check, so a strict editor and CI pass become the only thing standing between you and silently-wrong code, other is covered here.

For years, running TypeScript in Node.js meant choosing a tool: ts-node, tsx, Bun, Deno. Node.js 23.6 changed that with native TypeScript support. The --experimental-strip-types flag means the runtime can run .ts files directly by stripping type annotations before execution. No transpilation, no sourcemaps, no extra config, just node.

I've been using this on CLI scripts and internal tooling for a few months now. The startup time difference alone is worth understanding.

TL;DR:

  • node --experimental-strip-types server.ts runs .ts files directly in Node.js 23.6+ (Node.js v23.6.0 Release Notes)
  • Works with: type annotations, interfaces, generics, type aliases, anything that's pure TypeScript syntax
  • Does NOT work with: enums, namespaces, decorators, const enum, these generate JavaScript, not just syntax
  • Still need: tsc --noEmit in CI for type checking, tsconfig.json for editor support

How Does --experimental-strip-types Actually Work?

Type stripping is not transpilation. Node.js 23.6 uses the @swc/wasm-typescript package internally to parse TypeScript files and remove type annotations before handing the code to V8 (Node.js v23.6.0 Release Notes). The JavaScript that V8 sees is structurally identical to the TypeScript you wrote, minus all the type syntax.

What that means in practice: no ES feature downleveling, no decorator transforms, no type checking. If you write async/await, it stays async/await. Node.js executes the JavaScript that remains after stripping, using whatever V8 version ships with that Node.js release.

What TypeScript Syntax Is Supported

Everything that's purely a type annotation works fine. That includes:

  • Type annotations on variables, function parameters, and return types (const name: string = "hello")
  • Interface declarations and type aliases (interface User { id: number })
  • Generic type parameters (function identity<T>(val: T): T)
  • as type assertions and non-null assertions (value!)

Here's the simplest example. Save this as server.ts:

const port: number = 3000;

function greet(name: string): string {
  return `Hello, ${name}`;
}

console.log(greet("world"));
console.log(`Listening on port ${port}`);

Run it with:

node --experimental-strip-types server.ts

That's it. No config, no install.

What TypeScript Syntax Is NOT Supported

Enums are the main casualty. They generate real JavaScript, a lookup object, not just syntax. Same with namespaces: they compile to IIFE wrappers. Decorators (the kind NestJS and TypeORM rely on) aren't supported either.

// This will fail with --experimental-strip-types
enum Direction {
  Up,
  Down,
  Left,
  Right,
}

Replace enums with union types instead. See TypeScript patterns for the full argument, but the short version is that union types are more flexible and have no runtime overhead.

When Does This Replace ts-node and tsx?

Before I tried native stripping, I had --import=tsx in every Node.js script in our monorepo. That worked, but tsx 4.x adds roughly 80-150ms of startup overhead per invocation. For scripts that run hundreds of times during a build or test suite, that adds up fast.

Native type stripping adds near-zero overhead. The SWC-based stripping is synchronous and runs in the same process, no child process, no separate compiler pass.

The sweet spot for the native flag is:

  • One-off scripts, database migrations, data seeding, code generation tools
  • CLI utilities, internal tools you run from the command line
  • Development tooling, anything you're running locally that doesn't ship to production
  • Monorepo scripts, those scripts/ folders that were the whole reason you reached for tsx

Here's the before-and-after for a typical monorepo script:

// package.json, old way
"scripts": {
  "seed": "tsx scripts/seed-database.ts",
  "migrate": "tsx scripts/run-migrations.ts"
}
// package.json, new way (Node.js 23.6+)
"scripts": {
  "seed": "node --experimental-strip-types scripts/seed-database.ts",
  "migrate": "node --experimental-strip-types scripts/run-migrations.ts"
}

One fewer dependency. Same result.

If you're rewriting CLI tooling for the new runtime, our TypeScript clean code patterns collects the 15 patterns that age well in long-lived scripts, the kind of code that lives in scripts/ for years.

When Do You Still Need a Build Step?

Native type stripping is not a replacement for tsc. It's a convenience for running scripts. Several situations still require a full build:

Production applications. Type stripping skips type checking entirely. Every TypeScript error you'd normally catch with tsc --noEmit is invisible to the runtime. That means your CI pipeline must run tsc --noEmit as a separate step, it doesn't happen automatically just because Node.js can execute the file.

Decorator-heavy frameworks. NestJS and TypeORM both rely heavily on legacy decorators. The decorator transform hasn't been implemented in the strip-types path. Until that changes, these frameworks need ts-node or a build step with full tsc compilation.

Node.js 18 and 20 LTS environments. The flag exists only in Node.js 22.6+ and is more stable in 23.6+. If you're on an LTS version (which most production servers are), tsx 4.x is still your best option for script execution.

ESM/CJS interop edge cases. The strip-types path has some quirks around module resolution when mixing .ts files with CommonJS dependencies. Most projects won't hit these, but they exist.

The practical takeaway: use native stripping for scripts and local tooling. Use a build step for anything that ships.

How Do You Set Up a Zero-Config TypeScript Project?

The setup is genuinely minimal. You need two files.

The package.json scripts

{
  "name": "my-ts-project",
  "type": "module",
  "scripts": {
    "start": "node --experimental-strip-types src/index.ts",
    "typecheck": "tsc --noEmit",
    "dev": "node --watch --experimental-strip-types src/index.ts"
  }
}

The --watch flag pairs nicely with --experimental-strip-types. You get file-watching restarts without nodemon, without ts-node-dev, without any of the old toolchain.

The minimal tsconfig.json

You still want tsconfig.json. Your editor needs it. Your CI type check needs it.

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "noEmit": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*.ts"]
}

Set noEmit: true to make it clear this tsconfig is for checking only, not building. The actual execution goes through Node.js's strip-types path. See the strict TypeScript config article for what each strict flag actually enables.

The startup difference is hard to ignore: ts-node 10.x adds roughly 300ms before your code runs, tsx 4.x trims that to about 120ms, and Node native strip-types starts in around 15ms (community benchmarks, Node.js 23.6). For short-lived scripts, that overhead is most of the runtime.

What Does the Node.js 24 and LTS Roadmap Look Like?

Node.js 23 is the current non-LTS release. Node.js 24, expected in April 2026, will become the next LTS. The Node.js team has signaled that --experimental-strip-types will move toward stable in Node.js 24, possibly without the --experimental- prefix.

The Node.js team's stated goal is eventual support for type checking at the language level, not just stripping. That's a longer-term ambition, the current implementation deliberately avoids running the TypeScript compiler for performance reasons.

Bun was the main reason developers started expecting "just run TypeScript" as a feature. Deno made the same argument. Node.js is closing that gap faster than most expected. Honestly, ts-node is now legacy for any new project on Node.js 23+. That's not a criticism of ts-node, it solved a real problem for years, but the ecosystem has moved.

The interesting strategic question is what happens to tsx when strip-types goes stable. tsx's advantage was always startup speed over ts-node. Once the native flag is stable and out of the experimental namespace, that advantage disappears. tsx may survive as a compatibility shim for older Node.js versions, but its long-term role is unclear.

What's the Real-World Benefit?

Node.js finally has a path to running TypeScript without extra tooling. The tradeoff, no type checking at runtime, is fine as long as CI runs tsc --noEmit. That's not a new constraint. It's what every TypeScript project already does.

The developer experience improvement is real. One fewer dependency to install, one fewer tool to configure, one fewer reason to reach for Bun or Deno just to run a script. For scripts and internal tooling, node --experimental-strip-types is the right default now.

Frequently Asked Questions

Does --experimental-strip-types do any type checking?
No. The flag strips type annotations so Node.js can execute the file, it does not run the TypeScript compiler. Type errors are silently ignored at runtime. You must run tsc --noEmit separately in CI to catch type errors. Think of it as syntax removal, not compilation.
Can I use enums with --experimental-strip-types?
No. Enums generate actual JavaScript code during TypeScript compilation, they're not just syntax annotations. The strip-types flag only handles pure TypeScript syntax like type annotations, interfaces, and generics. Replace enums with union types or const objects to use native TypeScript execution.
Which Node.js version first supported --experimental-strip-types?
The --experimental-strip-types flag shipped in Node.js 22.6.0 as an unstable experiment, then stabilized further in Node.js 23.6.0 (Node.js v23.6.0 Release Notes). It is not available in Node.js 18 LTS or 20 LTS. For those environments, tsx 4.x or ts-node 10.x remain the options.
Do I still need a tsconfig.json with native TypeScript stripping?
Yes, for two reasons. Your editor (VS Code, WebStorm) needs tsconfig.json to provide IntelliSense, autocomplete, and inline error highlighting. And you need it to run tsc --noEmit in CI. The runtime doesn't require the file, but your workflow does. A minimal tsconfig with strict: true and target: ES2022 is enough.