Node.js is able to execute TypeScript files without additional configuration
194 comments
·August 17, 2025lunarcave
madeofpalk
This. It's 2025 and the node ecosystem is finally usable by default!
ESM modules just work with both Node and Typescript, Node can run .ts files, and there's the a good enough test runner built in. --watch. The better built in packages - `node:fs/promises` - are nice with top-level await for easier async loops.
It took a while to convince everyone involved to just be pragmatic, but it's nice now.
d357r0y3r
What's the story with supporting CommonJS libraries? I've tried to update many projects to ESM multiple times over the years, and every time, I ended up backing out because it turned out that there was some important upstream library that was still CommonJS - or even if we fixed those issues, our downstream NPM consumers wouldn't be able to consume EJS. So then you have to go down this rabbit hole of dual compilation, which actually means using something other than tsc.
ItsHarper
It's possible, but it can be weird and difficult: https://nodejs.org/docs/latest-v17.x/api/esm.html#esm_common...
Thankfully, actively-maintained CommonJS-only packages are quite rare by this point (in my experience).
> our downstream NPM consumers wouldn't be able to consume EJS
Node.js 20.17 and later supports loading ESM using `require()`: https://nodejs.org/api/modules.html#loading-ecmascript-modul...
The next version of Babel (currently in beta) is even going ESM-only.
miohtama
It's almost like Python 2 to Python 3 upgrade. Took a decade but everyone is finally happy.
hliyan
This is great to hear, but perhaps comes too late for people like myself. Node.js has been by go-to platform from around 2014 until last year. But around September last year, I found myself thrust into the .NET ecosystem (due to a client project). Within a few months, I realized that it too, had finally become usable by default (unlike the last time I tried it, when it was too tightly coupled to Windows). In fact, it felt like what Node.js would be, if it had strong typing built-in, and had a good standard library that eliminated a lot of the module management and churn. I'm now finding it hard to return to Node.js.
throwanem
Interesting. I haven't looked hard at .Net despite some advocacy from past colleagues. Perhaps I should.
benoau
Watching NodeJS fill in these gaps the last 5 years or so has been great, I strongly prefer using built-in stuff as much as possible now to avoid bloating the modules and becoming dependent on a thousand random people being good-stewards of their packages.
pseudosavant
I can’t help but think that none of these would have happened without Deno doing it first. It was basically the pragmatic Node before Node started to get reasonable.
benoau
That's kind of the whole history of NodeJS, dragged-forward kicking and screaming right from the 0.x and IO days!
balamatom
You mean TypeScript. TypeScript is finally usable by default.
edem
nah. it is still eons behind literally everything else
thrown-0825
does it have a go fmt / lint command yet?
Sammi
npx prettier
pjmlp
jslint/tslint are an install away.
sisve
Nope
_heimdall
I'm very much in favor of TS support directly in node. vitest has made it easier these days, but I've lost too much time over the years getting the balance just right when configuring test environments for .ts files.
trpc and ts-rest are a different animal in my opinion. I'm happy to use either one but won't deal with them in production. For trpc that's mainly due to the lack of owning API URLs and being able to more clearly manage deprecating old URLs gracefully.
For ts-rest I just tend to prefer owning that setup myself, usually with zod and shared typings for API request/response pairs. It also does irk me every time I import what is clearly an RPC tool named "-rest"
port11
vitest is incredible; it makes one wonder how/why jest, with its larger user base and community, couldn't get its TS support sorted.
rs186
Let's see if Sveltr converts their codebase back to TypeScript
reactordev
This is great up until you get to the fact that typescript will not be accepted under node_modules [0].
That leads me to ask, what about project dependencies? I wrote a lib for my data models in typescript and I want to import that into my app in node, in typescript? Does the rule only apply to npm packages? There’s opportunity here…
I wrote a runtime in golang that runs typescript (well, JavaScript in general). The grafana folks have sobek that all they need is to add type striping. I feel like if there’s one runtime where typescript could be adopted fully and it would change the world is Node.js. No transpiler, no typescript-go, no rust (well, maybe some rust ;) just a great parser that will keep track of the source map and types in debug mode (for tracing).
Either way, kudos to the node team, contributors, for pulling in the goal posts to make the kick to launch shorter. I’m still a fan of bun, and my own runtime, but node is the standard by which we all are kinda following. I also like that the embedding api is simple and clean to use now so if you want to make an executable, you can.
[0] https://nodejs.org/api/typescript.html#type-stripping-in-dep...
rovingeye
I made the same comment here https://news.ycombinator.com/item?id=44931575
"To discourage package authors from publishing packages written in TypeScript"
I tried to use it with private packages but that doesn't work either, apparently node doesn't even read the "private" field.
throwanem
"Written only in TypeScript" might put it better. If your module ships TypeScript source and a JS build as it should, then this will never affect it. Otherwise, to support stripping arbitrary modules would immediately compromise the design goal of light weight, due to the torrent of ill-founded and -formed bug reports incorrectly raised on Node that would follow. ("Don't make the maintainers' lives too miserable to continue the work" being also of course an implicit goal.)
rovingeye
Why would I want to ship a JS build for my private package? That's just extra machinery I don't need. Switching to a superior runtime would be easier.
reactordev
It’s a missed opportunity for sure
spankalee
Compile the packages to JavaScript before publishing. We absolutely should but be publishing TypeScript to npm.
rovingeye
I can understand the argument, since npm has no solution for TypeScript packages, unlike JSR:
"You publish TypeScript source, and JSR handles generating API docs, .d.ts files, and transpiling your code for cross-runtime compatibility."
Still would have been nice to have this for private packages.
This makes Deno/Bun much more attractive alternatives
sisve
Impressed with what Node is doing the last years, deno and bun has really made Node focus and improve. It was stuck for a while
mattlondon
What are the recent improvements in node itself?
Last actually note-worthy improvement I heard of was properly supporting import/export (although do you still need to use the .mjs hack?), but I've been out of the loop here for sometime so would be nice to know what they've added since.
pavlov
Here’s a nice overview:
https://kashw1n.com/blog/nodejs-2025/
It doesn’t cover everything, but as an old-school Node user I found several interesting features I didn’t know about.
koolba
That is a nice article. It does a great job summing thing up with real examples too.
9dev
Small but lovely addition for me is the ability to load .env files natively. There’s more like this; small, focused, real-world-improving features.
Tade0
> (although do you still need to use the .mjs hack?)
Syntax detection is enabled by default in v22.7.0, v20.19.0:
https://nodejs.org/api/packages.html#syntax-detection
Sounds like the obvious correct solution, making .cjs and .mjs obsolete - unless of course someone uses import() statements exclusively, in which case I need to ask: why?
hiimshort
It is surprising for me to see these features finally being added to Node after such a long time. Especially so when I remember reading discussion after discussion about how something like this wasn't possible. I touched on this in a blog post some time ago [1]. Glad Node is catching up.
madeofpalk
And for a few earlier versions `type: module` in package.json was all that was needed for .js files to be treated as ESM.
the_mitsuhiko
using, memory64, undici, async local storage, ESM import improvements, type stripping, local storage / session storage, env file support, built in file watching. Those are just the ones I mainly remember. There is a lot more.
franky47
Adding to the list: permissions, CLI styling/colouring, require(esm), globs, test runner.
moi2388
Do you mean.. node-worthy?
pjmlp
Have them though?
On the projects I am involved, they could even not exist, only node LTS releases matter, and the most recent projects are still node 20.
cheschire
As a quick aside, “them” is an object pronoun, not a subject pronoun. The correct word you needed is “they”.
You couldn’t phrase your original question as a statement “Them have though.” That’s often a quick test for valid English grammar. With the correct pronoun, it makes more sense: “They have though.”
As another example, take this sentence: “Have you seen them though?”
“You” is the subject of that sentence, and “them” is the object.
coldtea
Them is fine.
It's short for "Have them [Node bozos improved it], though?"
Or, equally likely it, refers to deno and bun ("deno and bun has really made Node focus and improve", "Have them (deno and bun) really made Node focus and improve, though?")
Sammi
22 is LTS. The future is now.
norman784
All even versions are LTS btw, maybe what you mean is that version 22 entered in maintenance mode (hence stable) and new features will not be added.
pjmlp
Sure, who is going to budget project upgrade effort of ensuring all dependencies work equally as well?
There is a reason why so many Java, Python, .NET/C#, C, C++,.. projects are stuck several versions behind.
rovingeye
https://github.com/nodejs/node/issues/57215
Not supporting type stripping in node_modules is unfortunate
quectophoton
But... that's like half the reason why I wanted this feature...
Writing a library in TypeScript (with typechecks in CI/CD as devDependencies) and just importing it directly from Node.js...
rovingeye
It was the first thing I tried and of course it didn't work.
It might finally be time to switch to Deno or Bun =(
chamomeal
I switched to deno for new projects ~1 year ago and it’s only been joy. There’s a shockingly small amount of friction to switch over, and there are so so many benefits
agrippanux
Bun has been awesome for me and my team fwiw
rmonvfer
I’m not a heavy JS/TS dev so here’s an honest question: why not use Bun and forget about node? Sure I understand that not every project is evergreen but isn’t Bun a much runtime in general? It supports TS execution from day 1, has much faster dependency resolution, better ergonomics… and I could keep going.
I know I’m just a single data point but I’ve had a lot of success migrating old node projects to bun (in fact I haven’t used node itself since Bun was made public)
Again, I might be saying something terribly stupid because JS/TS isn’t really my turf so please let me know if I’m missing something.
port11
I've mainly worked with Node for now 8 years, and recently switched to Deno. Even that switch was hard to do; not because things don't work, but you don't know when they won't.
Node has its share of flaws, but it's the de facto baseline against which things are tested and developed. I'm somewhat more comfortable working with The Main Thing.
The JavaScript ecosystem is nightmarish enough that many developers don't want to switch to the Next Cool Thing. I think many of us have had enough fatigue caused by new build tools, new bundlers, new runtimes, etc.
As of right now, Bun is not compelling enough for the potential headaches down the line.
(Maybe there won't be any, but I've spent weeks dealing with incompatibilities caused by a single TS minor update (which should've been breaking). Days chasing after dependency problems, after missing docs, etc.)
notsylver
I have tried fully switching to bun repeatedly since it came out and every time I got 90% of the way there only to hit a problem that couldn't be worked around. Last I tried I was still stuck on some libraries requiring napi functions that weren't implemented in bun yet, as well an issue I forget but it was vaguely something like `opendir` silently ignoring the `recursive` option causing a huge headache.
I'm waiting patiently for bun to catch up because I would love to switch but I don't think its ready for production use in larger projects yet. Even when things work, a lot of the bun-specific functionality sounds nice at first but feels like an afterthought in practice, and the documentation is far from the quality of node.js
ChocolateGod
I tried to replace Node with Bun, but had the following compatibility problems.
localAddress on TCP connections ignored, last time I tried it its no-op
Incompatibility with Node module APIs (https://github.com/spamscanner/spamscanner wouldn't work)
EventEmitter race problems (partially worked around with https://www.npmjs.com/package/eventemitter2)
Svelte vites dev server sometimes forever freeze until I wiped node_modules and reinstalled it.
motorest
> I’m not a heavy JS/TS dev so here’s an honest question: why not use Bun and forget about node?
Why would you switch from runtime A to runtime B? I mean, you presented no reason at all, let alone a compelling one, to pick either one. So what leads you to believe it is a reasonable idea to waste time switching runtimes?
Normal_gaussian
For me - it doesn't support secure and reliable dependency vendoring.
The best way to do this atm. is using (and configuring) yarn for zero-installs.
This keeps dependencies inside the codebase so that: * Issues can be easily traced to the code that actually ran - development and deployment are the same. * Deployment doesn't depend on package repositories. * Deployment is secure from many kinds of attacks. * It is possible to transparently patch packages. * Development is only internet dependent when adding a new package. * and the best ease-of-use - no reinstall when changing branches.
prmph
I agree. I've tried the Node TS and test runner features, and they are still (not yet) as good as Bun's. So for now sticking with Bun for those.
Really, in the Node ecosystem you eventually learn not to put all your eggs in one basket. Different things excel in different aspects. Here is my preferred setup for now:
Bun.js: As a Node runtime, and for TS execution and test running. I tried lots: TSX, TS-Node, Node itself
NPM For executing tooling scripts
PNPM For installing dependencies. It's simply better than the rest (npm, yarn, bun) for several reasons
Biome.js For linting (superior to every other tool I tried)
hungryhobbit
The bun test runner is definitely a lot better for testing than Node's.
But really, any test runner is beter than Node's: that thing is awful. It's like they looked at all the test runners in existence, and instead of copying what they all did, decided "let's make things harder for no apparent reason."
hungryhobbit
Bun is still a toddler: it's not ready for primetime.
Simple example: you know how at the command line you can type "npm run", and then type a character or two, hit tab, and the appropriate script from your `package.json` will autocomplete? And if you keep going (eg. "npm run knex") you can do the same thing to autocomplete arguments?
Bun still hasn't figured out how to do that (https://github.com/oven-sh/bun/issues/6037), even though they can all but copy NPM's (already written) completions. I really liked using bun when I played around with it (and it ran my codebase perfectly, without issue) ... but if they can't handle something as simple as Bash completions, they're clearly not ready for the big leagues.
gdorsi
The best thing is that they are shipping this as "type stripping" which means that there are no sourcemaps involved, making it zero-cost in production!
Very well done Node team!
adityapatadia
This change finally helped our company move to typescript.
Just converted many of services to TS and some are WIP. It’s a big win.
tempodox
It looks like this works by stripping away the type information, so at best it saves you a transpilation pass and doesn't improve safety.
nine_k
One of Typescript's design goals is that removing all type-related parts of the source text should yield a valid JavaScript file. A typescript compiler does not generate code (unlike, say, PureScript).
You can run a typechecker (such as tsc) that check various properties of your code statically, relying on the type information. It is then erased.
The same applies, say, to Python: type annotations are ignored at runtime. Somehow similarly, Java's type information is also partly erased in the bytecode; in particular, all the information about parametrized types. (This is to say nothing about actual machine code.)
9rx
> A typescript compiler does not generate code
Except for where it does: Enums, namespaces, parameter properties, etc.
MrJohz
This is true but these are also old features, and the TS team have stated that they will not add any more features like those, and to a certain extent regret adding them initially (particularly decorators, which iirc were added because the Angular framework wanted to use them). You can also see that these features aren't really being updated to match recent Typescript developments (parameter properties can't do true private properties, const enums don't work with isolated modules, etc).
I don't think those features are ever going to go away, because they've been around for so long and are so widely used. But I generally use erasableSyntaxOnly in new projects, because I find it's useful when my typescript source matches the generated Javascript code as much as possible.
mirekrusin
Just use erasableSyntaxOnly = true and you're fine.
hmry
Agreed about TS, but Python type annotations are not ignored. They are executed as code (all type annotations are valid expressions) and the results are stored on the module/class/function object that contains the annotated variable
nine_k
Python type annotations get turned into metadata which other tools may inspect at runtime, but the Python runtime itself does nothing with it. It's just well-structured comments.
In Python basically everything is executable, and so are type annotations.
kamov
Except in Python you can easily access the type hints at runtime, which allowed people to build ergonomic libraries such as Pydantic
fnord77
> Somehow similarly, Java's type information is also partly erased in the bytecode;
Just for generics, I believe
stevage
>so at best it saves you a transpilation pass and doesn't improve safety.
That's a bit misleading. Node being able to run TS code does not "improve safety", because that's not where the type checking happens. You can do type checking in your editor, or various other points in your toolchain.
Node being able to run TS code reduces the friction in writing TS code, which indirectly helps with type safety.
marcjschmidt
Except it doesn't. In anything serious, you have to wait for a full type check to happen before you run your TS code. Why would you run code that has not been checked yet and could throw very weird errors like undefined property access?
That just doesn't make sense. Yes, you can wait for your editor in your current open file, if you are lucky and the change in the open file doesn't break anything downstream in another file that is not yet open. In best case you have such simple code that nothing breaks, and in worst case, you have to still run it with type-checking - on top of running it in type-stripping-mode, because you got weird errors in runtime. This is a net negative.
This whole situation is there because we are trying to workaround the slow TSC. It's not a feature, it's something we actively work around. We try to whitewash now the obviously less useful "solution" of running code without its core features enabled: type checking. To me this is insane.
MrJohz
This is not my experience at all. In my experience, it's often quite useful being able to run code that doesn't fully type-check, as long as I do make sure everything's correct by the time I commit it. For example, I might be refactoring a module with some tests, and make a change that breaks the tests and some code in some other module. At this point, I often go in this order:
1. See that the tests aren't type checking correctly (usually for an obvious reason like adding an extra parameter or something). 2. Fix the tests using the type hints. 3. Run the tests to make sure my refactoring made sense and didn't break anything unexpected at runtime. 4. Fix all the uses in other modules.
Step 3 requires me to be able to run code that doesn't type-check correctly, and that's a useful feature.
There's also similar cases where I want to see how something looks in the UI even if it's not properly hooked up yet and causing type errors - I can check that part of the UI works and that it throws the correct runtime error (equivalent to whatever error typescript has). I've also had cases where I've cast things to `unknown` because I don't want to figure out the type just yet, and then written an implementation that is filled with typescript errors but will work at runtime as a mini proof of concept, only to later go back and get the types right.
I shall think you're underestimating how important fast cycle times are. When I'm developing, I normally have my linter, tsc, the dev server (tsx or vite), and the test runner all running simultaneously in watch mode. At any one point, I'm probably only interested in the output from one of these tools (the type checker until the types are all correct, then maybe the test runner until everything's green there). But if I run all of them at once, then they all run optimistically, and the tool I'm interested in is more likely to give me immediate feedback. That's really useful! Even with the new 10x typescript compiler, I'd still rather my tests start running immediately rather than waiting for another process to start and finish before they get going.
SCdF
You can tsc on the code and then ship that git hash if it passes. You don't need to run it every single time the code executes, nothing of value is gained, because nothing has changed.
sdfhbdf
TypeScript never promised improving safety, maybe it’s a common misconception. But TypeScript has no runtime mode or information. You were always at the mercy of running and not ignoring the typechecker. Nothing stopped you from running ts-node or tsx on code with egregious type errors. TypeScript is more like a linter in that regard.
MrJohz
I think it's not fair to say that Typescript isn't about improving safety, just that the mechanism isn't the same as with other languages. Typescript had always allowed you to ignore the type checker (in fact, the default configuration will always attempt to emit compiled Javascript, even if the source Typescript has type errors). But if you run the type checker on every commit (via e.g. CI or a precommit hook), then you can be sure that the code you release is correctly typed, which will not guarantee it is safe, but makes it more likely.
I agree that it's better to think of Typescript as a linter that needs specialised annotations to work, rather than a type system like you might find in Java or Rust.
tempodox
> TypeScript never promised improving safety
What, pray tell, would be the point of putting all that type information in there, and then have it checked (via tsc), if not for the sake of safety? What other use would this have in your opinion?
tkzed49
> TypeScript is more like a linter
that's exactly the point--GP is pointing out that node can't do that part
madeofpalk
Which is why the title is "Node can now execute Typescript files" and not lint, check, or even run TypeScript files.
resonious
Not needing a separate compile step just to run some script sounds great to me. I will run tsc if I want a type check.
marcjschmidt
There is always a compile step (JS -> Bytecode -> Machine code). The question is only if it is visible to you or not. They could have made it totally transparent to you by fully support TS including type checking under the hood including support full TS and not this subset of it, but decided not to do so. There is nothing inherently great to have less compile steps if you are not even aware of it. See v8 how many compile and optimizations steps they have - You don't care, because you don't see it. The only problem of TS is, you will always be able to see it because of it being slow.
I think running TS without type checks is almost entirely pointless.
ryuuseijin
I'm using tsx for a project to achieve the same effect. As you said, it saves you from having to set up a build/transpilation step, which is very useful for development. Tsx has a --watch feature built in as well, which allows me to run a server from the typescript source files and automatically restart on changes. Maybe with nodemon and this new node improvement this can now done without tsx.
To check types at runtime (if that can even be done in a useful way?) it would have to be built into v8, and I suppose that would be a whole rewrite.
rand0m4r
I agree, that said if the main reason people use TypeScript is security they should use a decent programming language instead.
lbltavares
Yes, it can't parse enums, for example.
dust42
It is available behind an experimental flag: --experimental-transform-types
seniorsassycat
Anecdotally Ive noticed a lot of packages failing to build when updating from node 20 to 22.18.0 that weren't failing with earlier node 22 versions. .18 unflagged typescript support.
These are packages using ts-node or tsx to run typescript in node, and with node 22.18 they seem to be using nodes native typescript support instead, and failing due to its limited feature set, or subtly different module resolution.
marcjschmidt
It's not able to execute TypeScript, but a subset of it. The claim in the title is misleading if not totally wrong.
This will unfortunately drive people towards using TS only as a linter, and not use its powerful features that are inherently impossible to implement with just type stripping.
bsimpson
I thought TS abandoned stuff that can't just be stripped. Besides enum, what do you use that isn't strippable?
chamomeal
I guess decorators. Which are quite powerful but I’ve never seen them used outside of libraries/frameworks that use them (angular, certain ORMs, nestjs?)
As a personal taste I don’t really like decorators that much, but it’s true that nestjs projects (which is probably a majority of new backend TS projects) will not gain anything from this release. Then again, you always set nestjs up with a template anyway that has all of the tooling and building baked in. So whatevs.
It’s still a huge huge win, and I finally have hope for typescript-ifying some horrible legacy node apps at work!!
null
tln
Kinda wild this default got flipped on in a minor version. For that matter the TS stripping went in a minor version too
seniorsassycat
> by replacing inline types with whitespace, Node.js can run TypeScript code without the need for source maps.
This is a really neat idea and I hope typescript adds this as a compiler option.
I think this + node:test makes Node.js a pretty compelling sensible default for most things now. Running things with `tsx` was such a QoL improvement when it happened, but it didn't solve everything.
Runtime type assertion at the edges is mostly solved through `zod` and tools like `ts-rest` and `trpc` makes it so much easier to do full-stack Typescript these days.