How to Run TypeScript Natively in Node.js

Node.js 23.6.0 marks a turning point for TypeScript developers: you can now execute .ts
files directly without transpilation tools like ts-node or tsc. This native TypeScript support streamlines development workflows by eliminating build steps while maintaining type safety through your IDE.
This guide covers everything you need to run TypeScript natively in Node.js, from basic setup to production considerations. You’ll learn how type stripping works, which TypeScript features are supported, and how to configure your projects for optimal compatibility.
Key Takeaways
- Node.js 23.6.0 executes TypeScript files directly without transpilation
- Type stripping removes annotations while preserving code structure
- Most common TypeScript features work, but enums and namespaces require workarounds
- Import statements must include the
.ts
extension - Native execution offers 2-3x faster startup times compared to traditional tools
Quick Start: Running TypeScript in Node.js 23.6.0
Let’s start with a simple TypeScript file to demonstrate native execution:
// greeting.ts
function greet(name: string): string {
return `Hello, ${name}!`
}
console.log(greet("TypeScript"))
With Node.js 23.6.0 or later, run this directly:
node greeting.ts
That’s it—no compilation step required. Node.js strips the type annotations and executes the remaining JavaScript.
To check your Node.js version:
node --version # Should be v23.6.0 or higher
You’ll see an experimental warning on first run:
ExperimentalWarning: Type Stripping is an experimental feature and might change at any time
To suppress this warning in development:
node --disable-warning=ExperimentalWarning greeting.ts
Or set it permanently:
export NODE_OPTIONS="--disable-warning=ExperimentalWarning"
Understanding Type Stripping in Node.js
Type stripping differs fundamentally from traditional TypeScript transpilation. Instead of transforming TypeScript into JavaScript, Node.js simply removes type annotations while preserving the original code structure.
Here’s what happens during type stripping:
// Original TypeScript
function calculate(a: number, b: number): number {
return a + b
}
// After type stripping (what Node.js executes)
function calculate(a , b ) {
return a + b
}
Notice the whitespace preservation—this maintains accurate line numbers for debugging without requiring source maps.
Performance Benefits
Native TypeScript execution offers significant performance improvements:
- No transpilation overhead: Direct execution without intermediate steps
- Faster startup times: ~45ms vs. 120ms with ts-node
- Reduced memory usage: No transpiler loaded in memory
- Simplified debugging: Original line numbers preserved
Evolution of TypeScript Support in Node.js
Understanding the progression helps you choose the right approach:
v22.6.0: Initial Type Stripping
node --experimental-strip-types app.ts
Basic type removal only—no TypeScript-specific syntax support.
v22.7.0: Type Transformation
node --experimental-transform-types app.ts
Added support for enums and namespaces through transformation.
v23.6.0: Default Type Stripping
node app.ts # Type stripping enabled by default
No flag needed for basic TypeScript execution.
Supported TypeScript Features and Limitations
What Works
Native TypeScript in Node.js supports most everyday TypeScript features:
// ✅ Type annotations
let count: number = 0
// ✅ Interfaces
interface User {
id: number
name: string
}
// ✅ Type aliases
type Status = 'active' | 'inactive'
// ✅ Generics
function identity<T>(value: T): T {
return value
}
// ✅ Type imports
import type { Config } from './types.ts'
What Doesn’t Work
Some TypeScript-specific features require workarounds:
Feature | Supported | Workaround |
---|---|---|
Enums | ❌ | Use union types or const objects |
Namespaces | ❌ | Use ES modules |
Parameter properties | ❌ | Explicit property declarations |
JSX/TSX | ❌ | Use traditional transpilation |
Decorators | ❌ | Wait for V8 support |
Example workarounds:
// ❌ Enum (not supported)
enum Status {
Active,
Inactive
}
// ✅ Union type alternative
type Status = 'active' | 'inactive'
// ✅ Const object alternative
const Status = {
Active: 'active',
Inactive: 'inactive'
} as const
Discover how at OpenReplay.com.
File Extensions and Module Systems
Node.js uses file extensions to determine module handling:
.ts
- Follows package.json"type"
field (ESM or CommonJS).mts
- Always treated as ESM.cts
- Always treated as CommonJS
Important: Local imports must include the .ts
extension:
// ❌ Traditional TypeScript
import { utils } from './utils'
// ✅ Node.js native TypeScript
import { utils } from './utils.ts'
Configuring tsconfig.json for Native TypeScript
While Node.js doesn’t read tsconfig.json during execution, proper configuration ensures IDE support and type checking:
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"moduleResolution": "nodenext",
"allowImportingTsExtensions": true,
"rewriteRelativeImportExtensions": false,
"verbatimModuleSyntax": true,
"strict": true,
"noEmit": true
}
}
Key settings explained:
allowImportingTsExtensions
: Permits.ts
in import pathsrewriteRelativeImportExtensions
: Set to false to preserve.ts
extensionsverbatimModuleSyntax
: Enforces explicit type importsnoEmit
: Prevents accidental JavaScript generation
Run type checking separately:
tsc --noEmit
Migration Guide from Traditional TypeScript Tools
Migrating from ts-node
- Remove ts-node dependency:
npm uninstall ts-node
- Update package.json scripts:
// Before
"scripts": {
"dev": "ts-node src/index.ts"
}
// After
"scripts": {
"dev": "node src/index.ts"
}
- Handle unsupported features (convert enums to unions)
Migrating from tsc Compilation
- Remove build steps:
// Remove these scripts
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
}
// Use this instead
"scripts": {
"start": "node src/index.ts"
}
- Update CI/CD pipelines to skip compilation
Performance Comparison
Native TypeScript execution shows significant improvements:
Metric | Native TypeScript | ts-node | tsc + node |
---|---|---|---|
Startup time | ~45ms | ~120ms | ~200ms |
Memory usage | Baseline | +30MB | +10MB |
Build step | None | None | Required |
Common Pitfalls and Solutions
Import Path Errors
// Error: Cannot find module './utils'
import { helper } from './utils'
// Solution: Include .ts extension
import { helper } from './utils.ts'
Unsupported Syntax
// Error: Enums are not supported
enum Color { Red, Blue }
// Solution: Use const object
const Color = { Red: 0, Blue: 1 } as const
Type Checking Confusion
Remember: Node.js doesn’t perform type checking. Always run tsc --noEmit
for type validation.
Production Considerations
When to Use Native TypeScript
- Development environments
- Prototypes and experiments
- Small scripts and utilities
- Teams ready for experimental features
When to Avoid
- Production applications (until stable)
- Projects requiring full TypeScript features
- Teams needing stable tooling
- Complex build pipelines
Docker Adjustments
# Before
FROM node:23-alpine
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
CMD ["node", "dist/index.js"]
# After
FROM node:23-alpine
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["node", "src/index.ts"]
Conclusion
Native TypeScript support in Node.js 23.6.0 represents a significant simplification for development workflows. By eliminating transpilation steps, you can focus on writing code rather than configuring build tools. While limitations exist—particularly around TypeScript-specific syntax—the benefits for most development scenarios are compelling.
Start by trying native TypeScript in your development environment, gradually migrate existing projects, and prepare for a future where TypeScript runs everywhere JavaScript does. As this feature stabilizes, expect broader adoption and expanded capabilities in upcoming Node.js releases.
FAQs
While technically possible, it's not recommended for production use yet. The feature is still experimental and may change. Use it for development environments, prototypes, and non-critical applications. For production, continue using traditional transpilation until the feature becomes stable.
Yes, you should keep TypeScript as a development dependency for type checking. Node.js only strips types without validating them. Run tsc --noEmit separately to catch type errors during development or in your CI pipeline.
Native TypeScript requires explicit .ts extensions in import paths, unlike traditional TypeScript tooling. Update all local imports from './module' to './module.ts'. External package imports don't need extensions.
Replace enums with union types for simple cases or const objects with as const assertion for numeric enums. For string enums, union types work perfectly. This approach is actually more tree-shakeable and aligns better with modern TypeScript practices.
The Node.js team focuses on type stripping rather than full transpilation for performance reasons. Features requiring runtime transformation like enums and decorators may get support when V8 implements them natively, but the goal is fast, minimal processing rather than complete TypeScript compatibility.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.