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 v22.6+ ships with native TypeScript support via type stripping, and as of v22 (LTS) it is enabled by default. You can now run .ts files directly.
node index.ts
That is it. No flags, no config, no extra dependencies.
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.
// 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:
- Set up a full build pipeline (overkill)
- Use
ts-nodeortsxas a dev dependency (an extra tool to manage)
Now the workflow for TypeScript scripts is identical to plain JavaScript scripts:
# No setup needed
node my-script.ts
This is especially impactful for:
-
CLI tools -- ship a single
.tsfile 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
Most TypeScript syntax works out of the box with type stripping:
- Type annotations, interfaces, and type aliases
- Generics
-
ascasts andsatisfies - Enums (numeric and string)
- Optional chaining and nullish coalescing
-
import typestatements
interface User {
id: number;
name: string;
role: 'admin' | 'user';
}
async function fetchUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`);
return 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 is not running the TypeScript compiler -- just removing types -- certain TypeScript features that emit JavaScript code cannot be supported:
Decorators (legacy)
Old-style decorators (experimentalDecorators: true) are not supported because they require code transformation, not just type removal. Modern Stage 3 decorators work fine.
const enum
const enum inlines values at compile time, which requires the TypeScript compiler to process the entire file. Type stripping cannot do this.
// This will fail with native type stripping
const enum Direction {
Up,
Down,
}
Use a regular enum instead -- it works fine.
Namespace imports with complex merging
Some advanced namespace patterns that rely on TypeScript's emit are not supported. Simple namespaces for typing purposes work.
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.
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:
# 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 requires v22.6+
- Performance in production -- pre-compiled JavaScript starts slightly faster than stripping types at runtime
-
Publishing npm packages -- publish
.jsfiles, not.ts
For applications, keep your build pipeline. For scripts and internal tools, skip it.
The Practical Workflow in 2026
Here is what a sensible setup looks like today:
// 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 const enum and legacy decorators. And for production applications, still build your code.
But for your next utility script? Just write .ts and run it. We earned this one.
Top comments (0)