TypeScript Without a Build Step: Native Type Stripping in Node.js

6 min readtypescriptnodejsjavascript

For years, running TypeScript in Node.js meant picking your poison: ts-node, tsx, esbuild, or setting up a full tsc pipeline. Every new project started with the same ceremony: install the compiler, configure tsconfig.json, wire up your scripts, and hope nothing breaks.

That era is ending. Node.js ships with native TypeScript support via type stripping. It was added in v22.6 behind a flag, enabled by default in v23.6 and v22.18, and declared stable in v25.2 and v24.12 (per the Node.js docs). On a current Node.js release you can run .ts files directly.

bash
node index.ts

That is it. No flags, no config, no extra dependencies.

How do you run TypeScript in Node.js without compiling? Use a version with type stripping enabled (v22.18+, v23.6+ or any v24 and newer) and run node file.ts. Node removes the type annotations and executes the JavaScript underneath. It does no type checking, ignores tsconfig.json, and does not support syntax that needs code generation, such as enum.


What Is Type Stripping?

Type stripping is exactly what it sounds like: Node.js reads your TypeScript file and strips out all the type annotations before executing it. The types are treated as comments: they are never checked, just removed.

typescript
// What you write
function greet(name: string): string {
  return `Hello, ${name}`;
}

// What Node.js sees
function greet(name) {
  return `Hello, ${name}`;
}

The runtime never sees the types. There is no type checking happening here. The types are there for your editor and your tsc runs. Node.js just ignores them and executes the JavaScript underneath.


Why This Is a Big Deal

Before this, even simple Node.js scripts in TypeScript required a setup step. If you wanted to write a quick automation script, a CLI tool, or a one-off data migration in TypeScript, you had two options:

  1. Set up a full build pipeline (overkill)
  2. Use ts-node or tsx as a dev dependency (an extra tool to manage)

Now the workflow for TypeScript scripts is identical to plain JavaScript scripts:

bash
# No setup needed
node my-script.ts

This is especially impactful for:

  • CLI tools: ship a single .ts file and let users run it directly
  • Scripts and automation: write TypeScript without the overhead
  • Monorepos: shared scripts no longer need their own build config
  • Quick prototyping: spin up a new file and run it instantly

What Still Works

Anything that can be removed without changing the JavaScript works:

  • Type annotations, interfaces, and type aliases
  • Generics
  • as casts and satisfies
  • Optional chaining and nullish coalescing
  • import type statements
typescript
interface User {
  id: number;
  name: string;
  role: 'admin' | 'user';
}

async function fetchUser(id: number): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  return (await res.json()) as User;
}

All of the above runs fine with node fetchUser.ts.


What Does Not Work

Type stripping has real limitations. Because Node.js only removes types and does not run the TypeScript compiler, any feature that has to be replaced with new JavaScript throws an error.

Enums, runtime namespaces and parameter properties

enum, namespace blocks that contain runtime code, and constructor parameter properties all need code generation. Node throws ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX. A namespace that only exports types is fine. Use a union of string literals or a plain object with as const instead of an enum.

typescript
// Fails with native type stripping
enum Direction {
  Up,
  Down,
}

// Works
const Direction = { Up: 0, Down: 1 } as const;

The --experimental-transform-types flag handled these cases in some versions, but the Node.js docs say it was removed in v26.0.0, so do not build on it.

Type-only imports need type

Without the type keyword, Node treats an import as a value import and fails at runtime.

typescript
import type { User } from './types.ts'; // works
import { fetchUser, type UserOptions } from './user.ts'; // works
import { User } from './types.ts'; // runtime error if User is only a type

File extensions and tsconfig.json

Imports must include the file extension, for example ./file.ts. Node ignores tsconfig.json, so paths aliases do not work (use subpath imports that start with # instead). Node also refuses to run TypeScript files inside node_modules, and .tsx files and legacy decorators are not supported.

For full TypeScript support, including tsconfig.json and enums, the Node.js docs recommend a third-party tool such as tsx: node --import=tsx your-file.ts.


No Type Checking

This is the most important thing to understand: Node.js does not check your types. If you pass a string where a number is expected, your code will still run and potentially fail at runtime. For full type-checking, you still need tsc, and TypeScript 7.0's Go-based compiler, released in June 2026, makes that roughly 8x to 12x faster. If your tsc --noEmit is slow today, the TypeScript performance guide walks through the patterns that cause it and the diagnostic loop to find them.

typescript
function add(a: number, b: number): number {
  return a + b;
}

add("hello", "world"); // No error from Node.js. Runs. Returns "helloworld".

Type checking is still your responsibility. Run tsc --noEmit separately in CI or as a pre-commit check:

bash
# Check types without emitting files
npx tsc --noEmit

Think of native TypeScript execution as separating two concerns that were always bundled together:

  • Type checking → tsc (you run this for correctness)
  • Execution → node (just runs the code)

When You Still Need a Build Step

Native type stripping is great for scripts, tools, and development. But for production, you still want to build:

  • Bundling for the browser: browsers do not support type stripping
  • Tree shaking and minification: you need a bundler for this
  • Targeting older Node.js versions: type stripping needs v22.18+ or v23.6+ to work without a flag
  • Performance in production: pre-compiled JavaScript starts slightly faster than stripping types at runtime
  • Publishing npm packages: publish .js files, not .ts

For applications, keep your build pipeline. For scripts and internal tools, skip it.


The Practical Workflow in 2026

Newer JavaScript features, such as the ES2026 features, depend on your Node.js version (its V8 engine), not on type stripping. If your Node.js version supports a feature, you can use it in a .ts file without a separate compilation step.

Here is what a sensible setup looks like today:

json
// package.json
{
  "scripts": {
    "dev": "node --watch src/index.ts",
    "typecheck": "tsc --noEmit",
    "build": "tsc"
  }
}
  • dev runs TypeScript directly with file watching, no compilation
  • typecheck validates your types without running the code
  • build compiles for production

You get the fast iteration loop of native execution during development, and proper type checking when you actually need it.


Conclusion

Native TypeScript execution in Node.js removes one of the most annoying parts of the JavaScript ecosystem: the mandatory build step for TypeScript scripts. For anything that lives on the server or runs as a tool, you can now write TypeScript and just run it.

The key things to keep in mind: types are stripped, not checked. Run tsc --noEmit for correctness. Avoid enums, runtime namespaces and legacy decorators, and use import type. And for production applications, still build your code.

But for your next utility script? Just write .ts and run it. We earned this one.

Frequently Asked

Which Node.js version can run TypeScript? Type stripping is on by default from v23.6 and v22.18, and the Node.js docs mark it stable in v24.12 and v25.2. It first appeared in v22.6 behind a flag.

Does Node.js type check TypeScript? No. It strips types and runs the JavaScript. Run tsc --noEmit for type checking.

Does Node.js support TypeScript enums? Not with type stripping. Use as const objects or string unions, or a tool like tsx.

Do I need tsx or ts-node now? Not for simple scripts on a recent Node.js. You still want one if you rely on tsconfig.json paths, enums or decorators.