Running TypeScript Files
TypeScript is JavaScript with types: you write price: number or course: "BCA" | "BSc", and the TypeScript compiler warns you about mistakes before the code runs. Many Node.js teams use it, especially on bigger projects. (The TypeScript tutorial teaches the language itself.)
For years, running TypeScript on Node.js needed an extra step: compile .ts files to .js first, or use a tool that compiles on the fly. Node.js 26 can run .ts files directly, with no flag and no extra package. In this lesson you will see how this type stripping works, which TypeScript features it supports, how to set up tsconfig.json to match, and why you still need the TypeScript compiler to check your types.
Running a .ts file
Here are two small TypeScript files for a college fee system:
// fees.ts
export interface Student {
name: string;
course: "BCA" | "BSc" | "BTech";
feesPaid: number;
}
export type FeeStatus = "paid" | "partial" | "due";
export function feeStatus(student: Student, totalFees: number): FeeStatus {
if (student.feesPaid >= totalFees) return "paid";
return student.feesPaid > 0 ? "partial" : "due";
}// app.ts
import { feeStatus } from "./fees.ts";
import type { Student } from "./fees.ts";
const students: Student[] = [
{ name: "Aarav", course: "BCA", feesPaid: 45000 },
{ name: "Priya", course: "BTech", feesPaid: 60000 },
{ name: "Kabir", course: "BSc", feesPaid: 0 },
];
const TOTAL: Record<Student["course"], number> = { BCA: 45000, BSc: 38000, BTech: 120000 };
for (const s of students) {
console.log(`${s.name.padEnd(6)} ${s.course.padEnd(6)} ${feeStatus(s, TOTAL[s.course])}`);
}node app.tsAarav BCA paid Priya BTech partial Kabir BSc due
No build step, no dist folder, no extra package. Watch mode (node --watch app.ts) and the test runner work with .ts files too.
How type stripping works
Node.js does not understand types. Instead it erases them: every type annotation, interface, type alias and import type line is replaced with blank space, and what is left is plain JavaScript, which V8 runs. Replacing with spaces (instead of deleting) keeps every line and column in the same place, so error stack traces point to the right spot in your .ts file.
This has two big consequences:
- No type checking happens. Node.js removes types; it does not check them.
- Only "erasable" TypeScript works. Features that generate JavaScript code cannot simply be erased.
Proof that types are not checked
// price.ts
function addGst(amount: number): number {
return Math.round(amount * 1.18);
}
console.log(addGst(500));
console.log(addGst("500"));590 590
The second call passes a string where a number is expected, which is a type error, yet Node.js runs it happily (JavaScript converts "500" to a number when multiplying). In a bigger program, such a mistake leads to bugs far away from the cause. Type checking is still the TypeScript compiler's job.
Checking types with tsc
Install TypeScript as a dev dependency and add a tsconfig.json that matches what Node.js can run:
npm install --save-dev typescript @types/node{
"compilerOptions": {
"noEmit": true,
"target": "esnext",
"module": "nodenext",
"rewriteRelativeImportExtensions": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"strict": true,
"types": ["node"]
}
}noEmit: only check; do not write.jsfiles, because Node.js runs the.tsfiles itself.module: "nodenext": follow Node.js's own module rules.erasableSyntaxOnly: report TypeScript features that type stripping cannot run.verbatimModuleSyntax: requireimport typefor type-only imports, as Node.js does.rewriteRelativeImportExtensions: allows.tsin import paths."types": ["node"]with the@types/nodepackage: type information forprocessand Node.js modules such asnode:fs. Current TypeScript versions do not load@typespackages unless you list them here.
Now run the checker:
npx tscbad.ts(1,21): error TS1484: 'Student' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled. enum.ts(1,6): error TS1294: This syntax is not allowed when 'erasableSyntaxOnly' is enabled. price.ts(6,20): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
The checker found the bad call in price.ts, plus two files (shown below) that Node.js could not run. Add "typecheck": "tsc" to your package.json scripts, run it before every commit, and run it in your automated checks. Your editor (VS Code) also shows these errors as you type.
What is not supported
These TypeScript features produce JavaScript code, so type stripping rejects them with the error code ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX:
| Feature | Example | Use instead |
|---|---|---|
enum | enum Course { BCA, BSc } | A const object with as const, or a union type |
namespace with code | namespace Utils { export const x = 1; } | ES modules |
| Parameter properties | constructor(private balance: number) {} | Declare the field and assign it in the constructor |
| Import aliases | import Fs = require("fs") | import fs from "node:fs" |
| Decorators | @Controller() | Not available without a build tool |
.tsx files | React components | A bundler or framework |
For example, running a file that uses an enum stops at once:
SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: x TypeScript enum is not supported in strip-only mode
,-[1:1]
1 | enum Course { BCA, BSc, BTech }
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^The usual replacement for an enum works everywhere:
export const Course = { BCA: "BCA", BSc: "BSc", BTech: "BTech" } as const;
export type Course = (typeof Course)[keyof typeof Course]; // "BCA" | "BSc" | "BTech"Other rules to remember:
- Write the
.tsextension in imports:import { feeStatus } from "./fees.ts". Without it you getERR_MODULE_NOT_FOUND. - Use
import typefor types.import { feeStatus, Student } from "./fees.ts"fails at run time withThe requested module './fees.ts' does not provide an export named 'Student', because the interface was erased fromfees.ts. Writeimport type { Student }orimport { feeStatus, type Student }. tsconfig.jsonis ignored by Node.js. Settings such aspathsaliases have no effect at run time..tsfiles follow the"type"field ofpackage.jsonlike.jsfiles;.mtsis always an ES module and.ctsis always CommonJS.- Node.js refuses to strip types from files inside
node_modules, so packages must still publish compiled JavaScript.
Common mistakes
- Thinking Node.js checks types. It does not; run
tsc(or your editor) to find type errors. - Leaving out
.tsin import paths. Node.js does not guess extensions in ES modules. - Importing an interface without
type. It no longer exists at run time. - Using
enumor parameter properties. Replace them withas constobjects and normal fields, and turn onerasableSyntaxOnlyso the compiler warns you. - Expecting
tsconfigpathsaliases to work. Use relative imports or theimportsfield ofpackage.json.
Key points
- Node.js 26 runs
.tsfiles directly by erasing types:node app.ts. - Type stripping is stable since Node.js 25.2 and 24.12;
--experimental-transform-typeswas removed in 26. - Only erasable syntax works: no
enum, runtimenamespace, parameter properties, decorators or.tsx. - Write
.tsextensions in imports and useimport typefor types. - Node.js never checks types; use
tscwithnoEmit,erasableSyntaxOnlyandverbatimModuleSyntax.
Exercise
This file does not run with Node.js type stripping. Rewrite it so that it runs with node shop.ts and passes tsc with the settings from this lesson. Keep the same behaviour.
enum Size { Small = "S", Medium = "M", Large = "L" }
class Kurta {
constructor(public name: string, public size: Size, private price: number) {}
label(): string {
return `${this.name} (${this.size}) ₹${this.price}`;
}
}
console.log(new Kurta("Chikankari kurta", Size.Medium, 1299).label());Show solution
// shop.ts
const Size = { Small: "S", Medium: "M", Large: "L" } as const;
type Size = (typeof Size)[keyof typeof Size];
class Kurta {
name: string;
size: Size;
#price: number;
constructor(name: string, size: Size, price: number) {
this.name = name;
this.size = size;
this.#price = price;
}
label(): string {
return `${this.name} (${this.size}) ₹${this.#price}`;
}
}
console.log(new Kurta("Chikankari kurta", Size.Medium, 1299).label());Chikankari kurta (M) ₹1299
The enum becomes a const object plus a matching union type, and the parameter properties become normal fields assigned in the constructor. A JavaScript private field (#price) replaces TypeScript's private, and it is private at run time too.
Quick Quiz
1. What does Node.js 26 do with the types in a .ts file?
Type stripping erases types without checking them; use tsc to check.
2. Which TypeScript feature does type stripping NOT support?
Enums generate JavaScript objects, so they cannot simply be erased.
3. Why does import { Student } from "./fees.ts" fail when Student is an interface?
Type-only imports must say type so Node.js removes them too.
4. Which tsconfig.json option makes the compiler report syntax that type stripping cannot run?
erasableSyntaxOnly flags enums, parameter properties and other non-erasable features.
Learn Node.js with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
