Skip to content(if available)orjump to list(if available)

Node.js is able to execute TypeScript files without additional configuration

lunarcave

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.

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!

firloop

It's still probably better to use Bun.

chrisweekly

Deno 2 is (arguably) just as compelling.

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

socalgal2

Does this run tsx? Even with the types stripped you still need the JSX transformed to JavaScript

wartijn_

It doesn’t. The comment you’re replying to is referring to tsx, the package that lets you execute ts files, not to running files with the tsx extension.

https://github.com/privatenumber/tsx

edem

i switched to python a while ago. it has batteries included. i feel so much better now that i dont have to debug all the quirks of a half-baked system.

wredcoll

Just wait, you'll find the python pain points at some point.

Two types of languages...

edem

What are the main pain points?

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.

[1] https://kilo.bytesize.xyz/an-incorrect-specification

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

epolanski

Build it.

rovingeye

I mean, you can just use a loader, as we've all been doing. It's already built, they just didn't implement it properly.

spankalee

No, is the right call. TypeScript has breaking changes. It would need to be standardized first.

rovingeye

What? All this does is strip types

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.

insin

Node has had a built-in --watch flag for a while too:

https://nodejs.org/docs/latest/api/cli.html#--watch

linhns

Surprisingly this gone unheralded.

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

medv

Exactly! Type checking can be long and costly.

null

[deleted]

akkad33

Ocaml does it fast

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

[deleted]

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.