Running TypeScript in Node.js: built-in type stripping vs tsx vs tsc
Node.js now runs .ts files directly by stripping types. Compare built-in type stripping with tsx and a full tsc build: what each supports, what breaks (enums, decorators, paths), and which to pick in 2026.
7 min read

In this article
Running TypeScript on the server used to require a build step or a loader: compile with tsc and run the JavaScript output, or start the app through ts-node or tsx. Node.js now runs many .ts files directly. Type stripping is enabled by default and, in the Node.js 24 documentation, marked stable since 24.12.0.
That raises a practical question for every new project: do you still need a TypeScript build or a third-party runner? The answer depends on which TypeScript features you use, whether you publish packages, and how you deploy. This article compares the three options available in October 2026, explains exactly what built-in type stripping does and does not support, and gives a recommended setup for a new service.
Everything below was checked against the Node.js 24 and 26 documentation and release notes on 7 October 2026, and the examples were run on Node.js 24.
The three ways to run TypeScript on Node.js
| Built-in type stripping | tsx (or a similar runner) | Compile with tsc, run JS | |
|---|---|---|---|
| Extra dependency at runtime | None | Yes | None (TypeScript is a dev dependency) |
| Type checking | No; run tsc separately | No; run tsc separately | Yes, during the build |
| Enums, namespaces with code, parameter properties | Not supported | Supported | Supported |
Reads tsconfig.json (paths, target) | No | Yes | Yes |
| Decorators | Not supported | Check the runner's documentation | Supported |
| Startup cost | Low | Loader transforms each file | None at runtime |
| Publishing to npm | Not suitable | Not suitable | Standard approach |
The Node.js documentation itself presents the choice in these terms: for full support of all TypeScript syntax and features, including tsconfig.json, use a third-party package such as tsx; for lightweight support, use the built-in type stripping.

How built-in type stripping works
Node.js replaces type annotations with whitespace and runs the result as JavaScript. Because the code positions do not move, stack traces point to the right lines without source maps. No type checking happens: a file with type errors runs as long as its JavaScript is valid.
The feature arrived in Node.js 22.6.0 behind a flag. It has been enabled by default since 23.6.0 on the 23.x line and since 22.18.0 on the 22.x line, stopped printing an experimental warning in 24.3.0, and was marked stable in 24.12.0. The Node.js 22 documentation still labels it a release candidate.
A small example. Note the .ts extension in the import and the type keyword on the type-only import:
// src/prices.ts
export type Currency = 'USD' | 'INR' | 'EUR';
export interface Price {
amount: number; // minor units, e.g. cents or paise
currency: Currency;
}
export function formatPrice(price: Price, locale = 'en-IN'): string {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: price.currency,
}).format(price.amount / 100);
}// src/main.ts
import { formatPrice, type Price } from './prices.ts';
const plan: Price = { amount: 1999, currency: 'USD' };
console.log(formatPrice(plan));$ node src/main.ts
$19.99There is no build step and no loader. With "type": "module" in package.json, .ts files follow the same module rules as .js files; .mts files are always ES modules and .cts files are always CommonJS.
What type stripping does not support
Anything that requires generating JavaScript, rather than deleting types, fails. The documentation lists the main cases:
enumdeclarationsnamespaceblocks that contain runtime code (type-only namespaces are fine)- parameter properties, such as
constructor(private db: Db) - import aliases (
import x = require(...)) - decorators, which Node.js will not support until JavaScript itself does
.tsxfiles, and TypeScript insidenode_modules
Here is what an enum does:
// src/bad.ts
enum Plan { Free, Pro }
console.log(Plan.Pro);$ node src/bad.ts
SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript enum is not supported in strip-only modeThe usual replacement is a constant object plus a derived union type, which type strips cleanly and gives the same autocompletion:
// erasable alternative to an enum
export const Plan = { Free: 0, Pro: 1 } as const;
export type Plan = (typeof Plan)[keyof typeof Plan];The transform flag is gone in Node.js 26
Node.js 22 and 24 offer --experimental-transform-types to handle enums and similar syntax by generating code. Node.js 26.0.0 removed that flag, as listed among its semver-major changes. If your code depends on it, either rewrite those constructs or use a full TypeScript runner or compiler before you move to Node.js 26.
Other rules that catch people out
- File extensions are mandatory in imports and
require()calls:import './file.ts', notimport './file'. - Type-only imports need the
typekeyword. Without it, Node.js treats the import as a value import and fails at runtime. TheverbatimModuleSyntaxcompiler option makestscenforce the same rule. tsconfig.jsonis ignored.pathsaliases do not work; use package.json subpath imports (which must start with#) instead.- No REPL support; TypeScript syntax is not supported in the REPL, with
--checkor ininspect.

Keep type checking: tsc as a linter
Because Node.js does not check types, you still want tsc in your workflow, in the editor and in CI. The Node.js documentation recommends TypeScript 5.8 or newer with these compiler options when you rely on type stripping:
{
"compilerOptions": {
"noEmit": true,
"target": "esnext",
"module": "nodenext",
"rewriteRelativeImportExtensions": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true
}
}erasableSyntaxOnly makes the compiler reject enums, parameter properties and other constructs that Node.js cannot strip, so you see the problem in the editor rather than at startup. noEmit fits services that only run .ts files; drop it if you also emit JavaScript, for example to publish a package.
A minimal package.json for a service then looks like this:
{
"type": "module",
"scripts": {
"start": "node src/main.ts",
"dev": "node --watch src/main.ts",
"typecheck": "tsc",
"test": "node --test"
},
"devDependencies": {
"typescript": ">=5.8"
}
}Pin the exact TypeScript version you install in your lockfile. node --test picks up *.test.ts files automatically, so tests need no extra setup either.
When to use tsx or a full compile instead
Choose a third-party runner such as tsx when you need TypeScript features that require code generation (enums, decorators used by frameworks, parameter properties) or you depend on tsconfig.json settings such as paths. The Node.js documentation shows two ways to start it:
npm install --save-dev tsx
npx tsx src/main.ts
# or
node --import=tsx src/main.tsChoose a full compile with tsc (or another bundler) when you:
- publish to npm. Node.js refuses to strip types inside
node_modules, deliberately discouraging packages that ship only TypeScript. Publish compiled JavaScript plus declaration files. - use a framework that relies on decorators and metadata, as several dependency-injection frameworks do.
- need to target an older runtime or change JavaScript output, which type stripping never does.
Migrating an existing project step by step
If you already run a service through a loader and want to try built-in type stripping, move in small, reversible steps:
- Upgrade Node.js first. Use 24.12.0 or newer on the 24.x line, where the feature is stable, or a current 22.x release where it is on by default.
- Turn on
erasableSyntaxOnlyandverbatimModuleSyntaxintsconfig.jsonand fix the errorstscreports. Typical fixes are replacing enums withas constobjects, turning parameter properties into ordinary fields assigned in the constructor, and addingtypeto type-only imports. - Add
.tsextensions to relative imports.rewriteRelativeImportExtensionsletstscrewrite them to.jsif you also emit JavaScript. - Replace
pathsaliases with subpath imports inpackage.json, for example"imports": { "#lib/*": "./src/lib/*" }. - Switch the start command from the loader to plain
node src/main.tsin one environment, run your tests and compare behaviour, then remove the loader dependency.
If step 2 produces hundreds of errors, for example because a framework relies on decorators, that is a sign to keep your current runner or build for this project and use type stripping for new scripts and smaller services instead.
A decision guide
| Your project | Suggested approach |
|---|---|
| New API, scripts or CLI tools, no decorators | Built-in type stripping, with tsc --noEmit in CI |
| Framework that uses decorators | The framework's own build (usually tsc) |
Existing code with many enums or paths aliases | tsx now; migrate gradually to erasable syntax if you want to drop it |
| npm library | Compile to JavaScript and declarations |
| Still on Node.js 20 | Upgrade first; 20.x is end of life |
Summary
- Node.js runs erasable TypeScript directly; the feature is stable from 24.12.0 and on by default in current 22.x releases.
- It strips types and does nothing else: no type checking, no
tsconfig.json, no enums, no decorators. --experimental-transform-typeswas removed in Node.js 26, so do not build on it.- Keep
tscas a type checker witherasableSyntaxOnlyandverbatimModuleSyntaxenabled. - Use tsx or a compiler when you need non-erasable features or publish packages.
Next step: enable erasableSyntaxOnly in an existing project and see how many errors it reports. That number tells you how close you are to dropping your TypeScript runtime loader.
Frequently asked questions
Does Node.js type-check TypeScript files?
No. It only replaces type syntax with whitespace and runs the JavaScript. Run
tsc(for example withnoEmit) in your editor and CI to catch type errors.Why does my enum fail with ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX?
Enums need generated JavaScript, which type stripping does not do. Replace the enum with an
as constobject and a union type, or run the file with a full TypeScript runner or compiler.Can I use tsconfig paths with built-in type stripping?
No. Node.js ignores
tsconfig.json. Use package.json subpath imports, which must start with#, or a runner that readstsconfig.json.Is --experimental-transform-types still available?
It exists in Node.js 22 and 24 as an experimental flag, but Node.js 26.0.0 removed it.
Your next customer could start right here.
Introduce your tool, product or business to readers exploring technology.
Explore placements and request a private quote
Sources
- Modules: TypeScript (Node.js v24 docs) — Node.js
- Modules: TypeScript (Node.js v22 docs) — Node.js
- Node.js 26.0.0 (Current) release notes — Node.js, 2026-05-05
- Node.js v26 changelog — Node.js
- Test runner: running tests from the command line — Node.js
Written with substantial AI assistance. Facts checked against cited sources. Editorial policy.
Comments
No comments yet. Be the first to join the conversation.
Leave a comment
Every comment is read by a moderator before it appears. Your email address is never published; it is used only to contact you about this comment.
More from Cloudsoft Zone

Industry updateNode.js
Which Node.js version to use in October 2026: LTS lines and the new schedule
Node.js 26 becomes LTS on 28 October 2026, Node.js 24 moves to maintenance and 20.x is already end of life. Here is every supported line, the new yearly release model from Node.js 27, and which version to run.
By Cloudsoft Zone

ComparisonNode.js
Express 5 vs Fastify 5: which Node.js framework to pick in 2026
Express 5 and Fastify 5 compared on support status, validation, error handling, upgrades and performance, with the same API built and tested in both.
By Cloudsoft Zone

TutorialNode.js
How to test a Node.js API with the built-in test runner (node:test)
Write, run, mock and measure coverage for a small Node.js HTTP API using only node:test and node:assert. Covers port-0 servers, fetch(), spies, t.mock.method(), fake dates and coverage thresholds.
By Cloudsoft Zone
