Okay so — you heard the thing. Node runs TypeScript now. No 'ts-node', no 'tsx', no build step, no config file staring at you before you've written a line of code. You make a '.ts' file, you type 'node app.ts', and it just runs. It feels like a magic trick, and I want you to actually do the trick before I tell you the part where it turns on you.
Maybe you already did this on your own project this week — dropped 'ts-node' out of your workflow, ran 'node app.ts' on real logic you actually wrote, not a tutorial's five-line snippet — and got that little hit of "wait, that's it?" I want you to get that hit again right now, on purpose, so the crash later lands on a feeling you actually had, not a warning you skimmed.
Open a folder, make one file, call it 'app.ts', and put this in it:
function shout(word: string): string {
return word.toUpperCase();
}
function formatPrice(cents: number): string {
const dollars = cents / 100;
return "$" + dollars.toFixed(2);
}
console.log(shout("node runs typescript now"));
console.log(formatPrice(1999));
Now run it. No compiler, no flag, nothing installed:
node app.ts NODE RUNS TYPESCRIPT NOW $19.99
That's it. That's the whole ceremony. A '.ts' file with real type annotations — 'word: string', 'cents: number' — ran straight through 'node' like it was always JavaScript. I remember the first time I did this I actually checked twice that I hadn't accidentally saved it as '.js'. You didn't. This is real, and it's older than the version you're probably crediting.
Quick version check before we go further, because I don't want you taking my word for a number that matters. Node's own TypeScript docs state it as a version line, not a vibe: "v23.6.0, v22.18.0 - Type stripping is enabled by default." Not Node 24. Not "the latest version." 22.18.0, which shipped before most of the "Node runs TypeScript now" posts you've probably seen even went up. Our QC editor made me confirm that line against Node's docs directly before this piece went anywhere near the site, and she was right to — "Node 24 did this" is the kind of tidy, wrong sentence that gets repeated forever once somebody writes it down.
What "runs your TypeScript" actually means
Here's the part I need you to sit with before you go build something real on top of this feeling: Node did not just check your code and pronounce it correct. It didn't check it at all.
What actually happened is called type stripping. Node reads your file, finds every bit of TypeScript syntax — 'word: string', 'cents: number', the whole annotation layer — and erases it back to plain JavaScript before running it. Node's own docs say this in language that doesn't leave room to misread it: "Node.js will replace TypeScript syntax with whitespace, and no type checking is performed." Whitespace. Not validation. Your types are a comment Node agrees to delete quietly on your way to running the file.
'tsc', the actual TypeScript compiler, does something completely different: it reads your annotations, checks whether every value flowing through your code actually matches what you promised, and refuses to hand you working code if it doesn't. Node skips that whole step. It trusts you. That's the trade — you get instant execution with zero setup, in exchange for handing over the one thing type annotations were for.
Okay so let's break it
Go back to 'app.ts'. This time we're writing something with an actual shape to it — a user object, not a canned example — because the trap doesn't show up in a two-line demo, it shows up in code you'd actually write.
type User = {
name: string;
age: number;
};
function describeUser(user: User): string {
const greeting = "Hey " + user.name.toUpperCase() + "!";
const ageLine = "You are " + user.age.toFixed(0) + " years old.";
return greeting + " " + ageLine;
}
const me: User = {
name: "sam",
age: "34", // typed as a string by mistake -- should be 34, not "34"
};
console.log(describeUser(me));
One typo. Quotes around '34' where a number should be. Your editor might even flag it with a red squiggle if it's running a TypeScript language server — but squiggles are advice, not enforcement, and nothing stops you from running the file anyway. Run it:
I'm not going to make you guess what happens — here's exactly what my terminal printed, on the same 22.22.2 install that ran the clean version above:
/path/to/app.ts:8 const ageLine = "You are " + user.age.toFixed(0) + " years old."; ^ TypeError: user.age.toFixed is not a function at describeUser (/path/to/app.ts:8:41)
No warning on the way in. No red text at the top telling you something's wrong with 'me'. The file just starts running — it's already built half your greeting before it hits the bad line — and then it dies, inside a function nowhere near where you actually typed the typo, on an error message that never mentions the word "type" at all. It just says a string doesn't have a '.toFixed' method, which is true, and tells you nothing about why a string ended up where a number was supposed to be.
The check Node isn't doing, run separately
This is the part that actually matters, so I want you to run it too. Install TypeScript for real — one command, and it's the same compiler every '.ts' file quietly assumes exists somewhere:
npm install --no-save typescript
Now ask it to check the same file, no build output, just the check:
npx tsc --noEmit app.ts
Here's what it told me, instantly, before a single line of the program ran:
app.ts(14,3): error TS2322: Type 'string' is not assignable to type 'number'.
Line 14. The exact line with the typo. No execution, no half-built greeting string, no stack trace pointing at the wrong location — just the compiler reading your promise and your value and telling you they don't match, before either of you has done anything. This is the check 'node app.ts' was never doing. It was never going to.
Why this matters
This is the same disease as the blast-radius bug this site covered a few weeks back — one edit, and the damage shows up somewhere else, later, looking unrelated to the actual mistake — just wearing a new door. 'node app.ts' feels like it's giving you TypeScript's safety net because it's reading a '.ts' file and your editor is still showing you red squiggles for real problems. It isn't the safety net. It's the fast lane with the net removed, and the net was never optional — it was the entire reason people reached for TypeScript over plain JavaScript in the first place. The no-build-step magic is completely real. It's just magic for running code, not for checking it, and conflating those two is exactly how a wrong-typed value gets deep into your app before anything tells you.
What to actually do with this
Keep running '.ts' files directly with 'node' — for scripts, for quick tools, for anything where you're the only one who'll ever run it, skipping 'ts-node' is a genuine, permanent win and I'm not walking that back. But before you trust a real project to it, add one line to your 'package.json':
{ "scripts": { "typecheck": "tsc --noEmit" } }
Run 'npm run typecheck' before you run the app, not after something breaks downstream. That's the whole fix, and it costs you one terminal command you weren't running before.
And the next time you catch yourself thinking "does Node typecheck '.ts' files now?" — you already know the answer, because you watched it not do that on your own screen. 'node app.ts' is not the same thing as running 'tsc'. One of them runs your program. The other one is the only thing that ever checked it. Node gave you the fast part for free. The part that actually catches your mistakes still has your name on it.