Node.js Native TypeScript Support: Toolchain 2026
Node.js can run TypeScript files directly in 2026. No ts-node, no tsx, no tsc compile step — just node file.ts. This has been the "I can't believe we need a tool for this" problem in the JavaScript ecosystem for a decade. It's now solved, with an important asterisk: Node.js strips types, it doesn't transform them. Understanding that distinction is the key to knowing what changes in your toolchain and what doesn't.
TL;DR
Node.js 22.18+ and 23.6+ run TypeScript files by default — just node file.ts, no flag, no ts-node. The underlying engine is Amaro, which strips type annotations without ever reading tsconfig.json or checking types. This works for most TypeScript code but doesn't handle syntax that requires code generation: enum, const enum, namespace, parameter properties (constructor(private x: T)), experimentalDecorators, path alias resolution, or JSX. For those, use --experimental-transform-types or keep a build step. For development scripts and tools, you can now delete ts-node (which has been in maintenance mode since 2022). For type checking, you still need tsc --noEmit. For production, you still need a bundler.
Key Takeaways
- Node.js 22.6+:
--experimental-strip-typesflag required - Node.js 22.18+ / 23.6+: on by default — just run
node file.ts, no flag needed - Powered by Amaro (wraps
@swc/wasm-typescript) — not the TypeScript compiler; never readstsconfig.json - What it does: strips type annotations (
: string,interface Foo {},type Bar = ...) using whitespace replacement - What it doesn't do: transform
enum,const enum,namespace,experimentalDecorators, parameter properties, or resolvepathsfrom tsconfig --experimental-transform-types: opt-in flag that enables enums, namespaces, and parameter properties (re-enters experimental)- You can remove:
ts-node(maintenance mode since 2022) for most development workflows - You still need:
tsc --noEmitfor type checking; a bundler for production;tsconfig.jsonfor IDE/LSP support package.jsontype:"type": "module"required for ESM TypeScript files to run directly
At a Glance
| Capability | Node.js Native | ts-node/tsx | tsc + node |
|---|---|---|---|
Run .ts files | ✅ | ✅ | ✅ (compiled) |
| Type checking | ❌ | ❌ | ✅ (with --noEmit) |
enum (legacy) | ❌ | ✅ | ✅ |
namespace | ❌ | ✅ | ✅ |
paths alias | ❌ | ✅ (with plugin) | ✅ (with tsc-paths) |
experimentalDecorators | ❌ | ✅ | ✅ |
| JSX/TSX | ❌ | ✅ | ✅ (with config) |
| Source maps | ✅ (v22.7+) | ✅ | ✅ |
| Node.js version | 22.6+ | Any | Any |
| Setup required | None (22.18+/23.6+) | npm install | tsc setup |
| Cold start overhead | None | ~200ms | Pre-compiled |
How It Works
Node.js uses Amaro (which wraps @swc/wasm-typescript) to strip type annotations — not the TypeScript compiler. Amaro is a fast, deliberate type eraser: it replaces type syntax with whitespace (not deletion), which keeps column numbers correct for stack traces without needing a full source map. Type stripping is purely additive: TypeScript-valid syntax that doesn't require transformation runs as-is. Node.js never reads tsconfig.json — it runs your TypeScript regardless of what's configured there.
// This TypeScript code runs directly with node --strip-types
interface User {
id: number
name: string
}
async function getUser(id: number): Promise<User> {
const response = await fetch(`/api/users/${id}`)
return response.json() as User
}
// After type stripping (what Node.js actually runs):
async function getUser(id) {
const response = await fetch(`/api/users/${id}`)
return response.json()
}
Enabling Native TypeScript Support
Node.js 22.6 - 23.5 (experimental flag):
node --experimental-strip-types server.ts
Node.js 23.6+ / 22.18+ (on by default — no flag needed):
# Just works — type stripping enabled by default, no flag required
node server.ts
# --watch for dev server behavior
node --watch src/index.ts
Via package.json scripts (22.18+ / 23.6+):
{
"scripts": {
"start": "node src/index.ts",
"dev": "node --watch src/index.ts"
}
}
With --experimental-transform-types (enables enums, namespaces, parameter properties):
# Opt back into experimental if your codebase uses transformed TypeScript features
node --experimental-transform-types server.ts
What Works: The Common Cases
TypeScript Interfaces and Type Aliases
// ✅ Works — interfaces are fully erased
interface ApiResponse<T> {
data: T
status: number
message: string
}
type UserId = string | number
type Handler = (req: Request, res: Response) => void
Generic Functions
// ✅ Works — generics are erased
function first<T>(arr: T[]): T | undefined {
return arr[0]
}
async function fetchJson<T>(url: string): Promise<T> {
const res = await fetch(url)
return res.json()
}
Type Assertions and Satisfies
// ✅ Works
const config = {
port: 3000,
host: 'localhost',
} satisfies ServerConfig
const user = data as User
const el = document.getElementById('app') as HTMLDivElement
Typed Parameters and Return Types
// ✅ Works — all annotations stripped
function createServer(config: ServerConfig): Server {
return new Server(config)
}
class UserService {
private readonly db: Database
constructor(db: Database) {
this.db = db
}
async findById(id: string): Promise<User | null> {
return this.db.users.findOne({ id })
}
}
What Doesn't Work: TypeScript Transforms
These features require a compilation step because they produce JavaScript output that doesn't exist in the source:
Enums (Legacy)
// ❌ Fails — regular enum compiles to a JS object (code generation required)
enum Direction {
Up,
Down,
Left,
Right,
}
// ✅ Use a plain const object — the modern, erasable alternative
const Direction = {
Up: 'UP',
Down: 'DOWN',
Left: 'LEFT',
Right: 'RIGHT',
} as const
type Direction = typeof Direction[keyof typeof Direction]
// ✅ OR use --experimental-transform-types if you must keep existing enums
// node --experimental-transform-types server.ts
Namespaces
// ❌ Fails — namespace compiles to an IIFE
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean
}
}
// ✅ Use modules instead
export interface StringValidator {
isAcceptable(s: string): boolean
}
Parameter Properties
// ❌ Fails — parameter properties emit field assignments (code generation)
class UserService {
constructor(private readonly db: Database) {}
// TypeScript generates: this.db = db ← Node.js can't do this
}
// ✅ Explicit property declaration instead
class UserService {
private readonly db: Database
constructor(db: Database) {
this.db = db
}
}
Path Aliases
// ❌ Path aliases in tsconfig.json DON'T resolve with --strip-types
// tsconfig.json: { "paths": { "@utils/*": ["./src/utils/*"] } }
import { formatDate } from '@utils/date' // ❌ Module not found at runtime
// ✅ Use actual relative paths (recommended for simplicity)
import { formatDate } from './src/utils/date'
// ✅ Or use import maps (Node.js native, no tsconfig needed)
// package.json:
// { "imports": { "#utils/*": "./src/utils/*.js" } }
import { formatDate } from '#utils/date' // ✅ Works with --strip-types
Updating Your Toolchain
What You Can Remove
For most projects, you can now delete these packages:
# ts-node: effectively unmaintained (last meaningful release 2022, maintenance mode)
# On Node.js 22.18+ / 23.6+, it's fully redundant for development use
npm uninstall ts-node
# Before (old scripts):
# "start": "ts-node src/index.ts"
# "migrate": "ts-node scripts/migrate.ts"
# After (Node.js 22.18+ / 23.6+):
# "start": "node src/index.ts" ← no flag needed
# "migrate": "node scripts/migrate.ts"
What You Keep
# Type checking — still essential
tsc --noEmit # Checks types without emitting files — keep in CI
# Production bundler — still needed for web apps
vite build # or tsup, esbuild, etc.
# tsup for Node.js server production builds
tsup src/index.ts # Fast ESM/CJS production build
The Recommended 2026 TypeScript Project Structure
Modern Node.js TypeScript project (scripts/tools/APIs) on Node.js 22.18+ / 23.6+:
development:
node --watch src/index.ts ← direct execution, no flag, no ts-node
type checking (CI/pre-commit):
tsc --noEmit ← still essential (Node.js never checks types)
production (Node.js server):
tsup src/index.ts --format esm,cjs ← optimized build
production (web app):
vite build ← still the right tool, handles JSX + optimizations
Mental model shift:
Node.js = the development runner
tsc = the linter/type-checker
Build tools = production artifacts only
tsconfig Changes
With native TypeScript support, your tsconfig becomes "type checking only" — it doesn't control how Node.js runs your code:
// tsconfig.json — 2026 recommended for Node.js projects
{
"compilerOptions": {
"target": "ESNext",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noEmit": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Source Maps and Debugging
Node.js 22.7+ supports --enable-source-maps alongside --strip-types:
node --strip-types --enable-source-maps src/index.ts
With source maps enabled, error stack traces point to TypeScript line numbers, not the stripped JavaScript positions. Debuggers (VS Code, WebStorm) can set breakpoints in .ts files and hit them correctly.
The Bigger Picture: What This Means
Node.js native TypeScript support doesn't replace your entire toolchain — but it removes a real friction point for scripts, CLIs, tooling, and simple APIs. The impact is most felt in:
- Development scripts and automation — no more ts-node config, just
node file.ts - CLI tools — ship as TypeScript source, users run with Node.js directly
- Monorepo tooling — internal scripts that only run in a development context
- Simple APIs and microservices — remove the ts compilation step from the dev loop
For web apps, production Node.js servers that need optimization, or anything using JSX, you still want a bundler (Vite, tsup, esbuild). Node.js native TypeScript support solves the "running a TypeScript file" problem, not the "deploying a production application" problem.
The mental model shift this enables is worth articulating clearly. Before Node.js 22.18+, TypeScript was a language that required a compile step before execution — similar to how Go or Rust produce binaries before running. The compile step was fast (ts-node made it nearly transparent) but it was a conceptual barrier. Developers had to understand that their TypeScript source was being transformed before Node.js saw it. With native stripping, TypeScript becomes a language that Node.js runs directly, with types being treated as comments. This aligns TypeScript more closely with how Python type annotations work: they're present in source but ignored at runtime.
This conceptual shift has real consequences for developer onboarding and tooling simplicity. Internal tools and automation scripts at companies that previously had no TypeScript entry points can now be written in TypeScript without any infrastructure setup. A developer who knows TypeScript but has never set up a TypeScript project can write node script.ts and have it work. The barrier between "I know TypeScript syntax" and "I can write and run a TypeScript program" is essentially gone for simple cases.
Ecosystem and Community Impact
The announcement of native TypeScript stripping in Node.js 22.6 was one of the most discussed JavaScript ecosystem events of 2024. The PR thread on the Node.js GitHub repository had hundreds of comments debating the right approach — some argued for full TypeScript compilation support (checking types as well as stripping them), others preferred the minimal approach that was adopted. The final decision to use Amaro/SWC for stripping without type checking was pragmatic: type checking in Node.js core would require shipping the TypeScript compiler (a massive dependency) and would be significantly slower.
The practical impact on the ecosystem has been significant. ts-node's maintainer has acknowledged the package is in maintenance mode — it will receive bug fixes but no new features, and new projects should use Node.js native TypeScript support. The tsx package (a popular ts-node alternative that used esbuild) continues to be useful for projects that need enum support, path aliases, or JSX without adding build steps, but its usage rationale has narrowed considerably.
For TypeScript beginners, native support removes a major onboarding friction point. Previously, a new TypeScript developer needed to understand tsconfig, ts-node configuration, and the compilation pipeline before writing their first line of business logic. Now, npm init + node index.ts just works on any modern Node.js version.
Real-World Adoption
The migration away from ts-node in existing projects has been widespread among Node.js teams that track ecosystem changes closely. Developer tooling projects — CLIs, build tools, code generators — were among the first to migrate because they often have simple TypeScript code without enums or path aliases, making the migration a one-line change in package.json scripts.
Backend API projects have been slower to migrate because many use enums for status codes and roles, and parameter properties in service classes. These teams are using --experimental-transform-types as a bridge while gradually refactoring enums to const objects. The refactoring is generally considered an improvement in code quality regardless — const objects are more composable than TypeScript enums, which famously behave unexpectedly in several edge cases.
Framework and library authors have generally updated their getting-started guides to use native TypeScript support. Hono's documentation, for example, now shows node --watch src/index.ts in its Node.js quickstart rather than requiring ts-node setup. This represents a meaningful DX improvement for developers getting started with the framework.
The npm ecosystem itself is adapting. Several popular Node.js packages have updated their package.json engines field to require Node.js 22.18+ and are now distributing TypeScript source directly rather than compiled JavaScript. This is particularly common for development tooling packages — code formatters, linters, and code generators — where the development overhead of maintaining a compilation step for a tool that only runs in development was hard to justify. Library authors are more conservative about requiring a specific Node.js version for production packages, but the trend toward TypeScript source distribution is clear for tooling.
Team onboarding metrics at companies that have adopted native TypeScript support tell a compelling story. The time from "new developer joins the team" to "first successful TypeScript script execution" dropped from hours (setting up ts-node, tsconfig, resolving module compatibility issues) to minutes (install Node.js 22.18+, write a .ts file, run it). This reduction in setup friction has real impact on developer productivity and morale, particularly for developers joining TypeScript projects for the first time.
CI/CD pipelines also benefit. Build stages that previously needed ts-node in devDependencies — just to run a database migration script or a seed file — no longer require that install step. Smaller devDependency trees mean faster npm install times in CI, which compounds across many pipeline runs. Organizations running hundreds of CI pipelines per day report measurable savings from this reduction in dependency overhead. The zero-dependency nature of Node.js native TypeScript support is one of its most underappreciated advantages at scale.
Performance Analysis
The stripping approach rather than compilation has a meaningful performance advantage. Amaro/SWC processes TypeScript files in microseconds — it's doing simple text replacement (whitespace for type annotations) rather than parsing a full AST and generating optimized JavaScript. There's no startup overhead compared to running ts-node, which was typically 200-500ms slower per invocation due to TypeScript compiler initialization.
For long-running processes like servers, the startup overhead difference is negligible either way. The performance benefit is most noticeable in scripts and tools that get invoked frequently — database migration scripts, code generators, test setup scripts. These previously had a painful ts-node startup tax that made them feel slow even when the actual logic was fast.
The node --watch mode (equivalent to nodemon for TypeScript files) reruns the process on file changes. Because there's no compilation step, the restart is essentially instantaneous — comparable to plain JavaScript hot reload. This is a genuine DX improvement for development server workflows where you previously needed nodemon + ts-node or tsx to get similar behavior.
Migration Guide
Migrating a ts-node project to native TypeScript is typically a one-day task for most projects. Start by upgrading to Node.js 22.18+ or 23.6+. Replace all ts-node src/... script calls with node src/.... Run your test suite — if everything passes, you're done for the majority of the codebase. For failing files, the error messages are clear: enums, namespaces, and parameter properties will throw at startup with explicit messages.
The most common migration blockers are enums and parameter properties. For enums, the refactoring to const objects is mechanical: replace enum Status { Active = 'active', Inactive = 'inactive' } with const Status = { Active: 'active', Inactive: 'inactive' } as const and type Status = typeof Status[keyof typeof Status]. For parameter properties, add explicit field declarations as shown in the code examples above.
Migrating path aliases is more involved. If your project uses @/ or @utils/ style imports, you have two options: switch to Node.js import maps (using the imports field in package.json) or switch to relative imports. Import maps are the more principled solution — they work with native TypeScript support and are a standard Node.js feature. The one-time migration is worth the cleaner long-term outcome.
When to keep tsx instead of migrating — not every TypeScript project is ready for native stripping. If your codebase uses TSX files, experimentalDecorators, or path aliases, tsx (or --experimental-transform-types) is still the right tool. tsx handles TSX files via esbuild and supports all TypeScript features without restriction. The ts-node-to-tsx migration is faster than the ts-node-to-native migration because tsx is fully TypeScript compatible. For teams that need an intermediate step, migrating from ts-node to tsx first is a valid approach — tsx is faster than ts-node and handles edge cases better, and you can then evaluate native stripping as a further simplification. See best TypeScript build tools 2026 for how tsup, esbuild, and SWC fit into the broader build pipeline alongside native TypeScript support.
Testing the migration thoroughly before removing ts-node from production involves more than running the unit test suite. Integration tests that spin up the actual server process and make HTTP requests catch runtime errors that unit tests miss. Specifically, test any module that uses dynamic imports or conditional requires, since ESM and CJS interop behavior can differ between Node.js versions. The --experimental-strip-types mode in older Node.js versions (22.6-22.17) is stricter than the stable version in 22.18+ — if you're testing against an older Node.js version, some code that fails with the experimental flag will work fine with the stable version. For comprehensive testing tooling alongside this runtime evaluation, see best JavaScript testing frameworks 2026.
Rollback strategy — if issues emerge after removing ts-node from production, the rollback is a package.json script change: add ts-node back as a dev dependency and update your start script. This is a low-risk migration because the change is isolated to dev tooling, not application logic. The Node.js code that ts-node runs is identical to what Node.js native stripping runs — only the transform layer changes, not the application behavior.
Final Verdict 2026
Node.js native TypeScript support is the right evolution for the ecosystem. The type-stripping approach is correct — trying to do full TypeScript compilation inside Node.js core would have been complex, slow, and difficult to version-align with TypeScript releases. The Amaro/SWC approach gives you fast, dependency-free TypeScript execution for the majority of TypeScript codebases.
The practical impact is most felt in tooling and scripts, where ts-node was a constant friction point. For production applications, the toolchain remains largely unchanged — type checking with tsc --noEmit and bundling for production artifacts. But the development workflow simplification is real and meaningful, especially for teams onboarding new developers who can now write TypeScript without understanding the compilation pipeline.
If your project uses modern TypeScript without legacy features (no enums, no namespaces, no parameter properties), upgrading to Node.js 22.18+ and removing ts-node is a clean, low-risk improvement with immediate developer experience benefits.
When to Keep tsx or ts-node
Not every TypeScript project benefits immediately from Node.js native stripping. Understanding when to keep your existing tools saves time on premature migrations.
Keep tsx if your codebase uses TSX files (React components in .tsx format) that run in Node.js for server-side rendering, server components, or tests. Native Node.js TypeScript stripping cannot process JSX syntax — it will throw a syntax error when it encounters JSX tags. tsx handles TSX files natively via esbuild, making it the right tool for any server-side JSX use case.
Keep ts-node (or migrate to tsx rather than native stripping) if your project uses decorators extensively — particularly NestJS, TypeORM, or class-validator with experimentalDecorators. The --experimental-transform-types flag adds support for these transforms but takes the feature out of "stable" status. For production NestJS applications, the stability tradeoff isn't worth it yet.
Keep a compilation step if your TypeScript uses path aliases (@/ imports), tsconfig.json project references, or advanced module resolution features. Native TypeScript stripping ignores tsconfig.json entirely, so none of these features work. Migrating to Node.js import maps (the imports field in package.json) provides a standards-based alternative, but it requires refactoring all import paths — a significant investment for large codebases.
For teams in the happy path — plain TypeScript with interfaces, generics, and type annotations on a backend that doesn't need JSX or legacy transforms — the migration is genuinely straightforward and worth doing. The best JavaScript runtimes 2026 comparison covers Bun and Deno's native TypeScript approaches for teams evaluating runtime alternatives. If you're making build tooling decisions alongside the TypeScript runtime choice, see best TypeScript build tools 2026 for production bundler options.
Comparison with Alternative Runtimes
The native TypeScript support in Node.js changes the competitive landscape with Bun and Deno, both of which have always run TypeScript natively. Understanding the differences matters for teams choosing their runtime.
Bun has run TypeScript natively since its first release in 2022. Bun uses the JavaScriptCore engine (not V8) and its own bundler, with TypeScript stripping implemented via a custom Zig implementation. Bun is measurably faster than Node.js for startup-heavy workloads — a cold bun run file.ts is typically 3-5x faster than Node.js. However, Bun's compatibility with the full Node.js ecosystem (particularly native modules and certain Node.js APIs) still has edge cases that require workarounds in production.
Deno has run TypeScript natively since 2018 via a first-class TypeScript compiler integration. Unlike Node.js and Bun, Deno type-checks TypeScript by default (with --no-check available for faster execution). Deno's native TypeScript support is the most complete — it actually validates types rather than stripping them. The tradeoff is that deno run with type checking is slower than Node.js's stripping approach.
With Node.js now matching Bun and Deno on the basic "run TypeScript without a build step" capability, the runtime choice comes down to ecosystem compatibility (Node.js wins), performance (Bun leads for specific workloads), or security model preferences (Deno's permission system is unique). For teams running on Node.js in production, upgrading to 22.18+ and using native TypeScript support is the lowest-friction path rather than switching runtimes.
The key remaining advantage of tsx over native Node.js TypeScript support is JSX processing — tsx handles TSX files correctly, while Node.js native stripping cannot. For teams building server-side JSX rendering or testing React components in Node.js, tsx (or a build step via tsup/esbuild) remains necessary. For pure backend TypeScript without JSX, native Node.js support covers all common patterns.
Compare ts-node, tsx, and tsup npm downloads on PkgPulse.
Related: Best Monorepo Tools 2026, Hono vs Elysia 2026, Best JavaScript Testing Frameworks 2026
Also related: ts-blank-space vs Node strip-types vs swc · Bun vs Node.js 2026 · ECMAScript 2026 Features