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.
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.
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.
ESM, --watch, and TS syntax support is the combo that makes it all good!
The slow adoption of ESM by Node, with many compatibility missteps, the thousand papercuts around TS, the way frontend-centric toolchains kinda-sorta paper over the whole thing, letting it fester, and the way people have been acting like things are ready for primetime for over a decade while diligently testing them in production, all of that came later. To the point of having me wondering how did people work with TypeScript before ~5.4 - though evidently they did, and had few if any of the same complaints!
Baffling but IIWII. Anyway, only this year I discovered a pure `tsx` + ESM workflow had become viable OOTB, to no little surprise. I perceive that as the toolchain becoming unfucked just as randomly as it became fucked when Node 16 did what it did. Not that it didn't take a couple years for TS to "invent" the right compiler flags that it took to tell it to stay out of the runtime's way, too.
So a good year overall. Hope they don't break it again because when they do it's an uphill struggle to convince them that they have.
npx prettier
My fear with Biome is missing out on type-aware lints, but I know Oxlint has had some success integrating the new Go typescript compiler, so maybe that will work out for Biome as well.
Genius!
Arguably, code formatters should be configurable, to get a format for your code that you want. Unfortunately, prettier isn't one, and it is a form of regression in many communities at the cost of choice pruning.
It might be great for a CI pipeline for constraining how code should look (use prettier, dumbass!), but it isn't great for actually formatting code, as it just makes the code "prettier".
Even though prettier has defaults, but they can be modified to quite some extent to suit your projects needs: https://prettier.io/docs/options
And we had huge dependency chains as well to non-standard library stuff, nowhere near as bad as an average nodejs project but still not free from the problem.
Seriously though it is nice, but migrating away from your existing tooling is painful and underappreciated.
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"
Two types of languages...
Python libraries are much more stable and reliable.
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...
"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.
"You publish TypeScript source, and JSR handles generating API docs, .d.ts files, and transpiling your code for cross-runtime compatibility."
Not allowing it for private modules doesn't make much sense to me, though. It either forces me to use a loader, or now figure out a JS build step which I have been more than happy to avoid up until now.
As a past and present module author, I don't feel myself unduly burdened by the need to maintain the tooling to support the work I publish. Or present-ish, anyway; last time around it was Bower and about Node 8, and the experience these days is worlds more comfortable.
I can see why others would feel differently, but again, I'm not really here to argue preferences. If you'd like to fill your afternoon instead with an enjoyable, on-theme read, try the UNIX-HATERS Handbook: https://web.mit.edu/~simsong/www/ugh.pdf
"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
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.
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.
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?
Additionally, io.js actually forked off due to internal drama which started with Ben Noordhuis having changed some pronouns here and there and people wanting to cancel him for that, to which he picked up his toys and left the sandbox.
It so happened that aside from being competent himself, he had competent people on his side, which eventually forced those governing Node.js to concede.
Bun is just a cash grab in comparison.
And the biggest issue with Node IMO is that the standard lib still forces you to rely on endless npm dependencies.
Node is still very much stuck.
How? We have async/await file access, a async/await test runner, and even async/await sleep built in. What are you missing?
Everything else needed to make a backend app.
At the very least, Node should provide fundamental pieces like database drivers. Currently the best PG driver[1] depends on a single guy.
Bun already provides its own PG driver [2] and Jarred has written they will keep investing into more built-in APIs.
Definitely a problem, but funding good Postgres/MongoDB/SQLite should be handled by AWS, Microsoft, Google, and other orgs that sell database services.
> Definitely a problem, but funding good Postgres/MongoDB/SQLite should be handled by AWS, Microsoft, Google, and other orgs that sell database services.
A good chunk of PG development is done by employees of those companies (*). Of course they could (and probably should) always do more. But even if they invest more, it's not obvious that the marginal effort is best invested in some language's drivers...
Disclaimer: I'm paid by one of those big companies.
I am not saying this problem exists and 19% is a made up number, the grandparent post seems to think there is some kind of problem.
Just as an example, Express depends on 25 modules with a single maintainer.
https://npmgraph.js.org/?q=express
Obviously a router is a fraction of what's needed for any non trivial backend project.
People were cuddling and pasting code from random people on the Internet they didn't understand for many years before package managers where there were zero maintainers. Many people that don't properly understand supply chain issues still are.
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.
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.
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?")
Without the expansion I don't know of any native English speaker who would say it.
There is a reason why so many Java, Python, .NET/C#, C, C++,.. projects are stuck several versions behind.
Not supporting type stripping in node_modules is unfortunate
Writing a library in TypeScript (with typechecks in CI/CD as devDependencies) and just importing it directly from Node.js...
It might finally be time to switch to Deno or Bun =(
Bun has been great.
Deno... perhaps the less said, the better.
My project config is weird (slightly more sophisticated that my repro), so, it's probably on me. Otherwise I absolutely love Deno. It makes TypeScript simple and joyful. It's the simplicity this language/tooling ecosystems badly needs in my opinion. Sometimes I feel like it makes TypeScript feel a bit more like working with Go; you can just throw a main.ts in there and build an excellent CLI from it in minutes.
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.
Because Node is controlled by and maintained via the OpenJS Foundation. Bun is a venture-backed startup. That would be fine, but multiple, direct requests from me to the founder (Jared) about what the business model is (what I personally need to say "yes" or "no") have gone without response every time.
That led me to the assumption that Bun may very well be technically superior (or at least on the track to being so), but I can't bet anything significant or long-term on it. I need to know that this isn't just an exit vehicle for the founder (masquerading as a desire to fix JS server runtimes). Silence to a simple (dare I say, obvious) question doesn't bode well.
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.)
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.
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
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)
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."
I dislike being on the bleeding edge for things. NodeJS is the most supported in the JS ecosystem. I find it much better to just be on the "default" option for things. You know, choose boring technology.
Is it a boring technology? I remember trying Node.js when it just came out, and I don't think all that much improved. Whole node.js always felt one step forward, two steps back. A lot of early design decisions still hurt it.
I would call it stagnated before I call it boring.
If, for whatever reason, you need to run JS outside the browser, then Node seems the most boring of all the possible options. Certainly "rewriting all our JS code in Java" sounds a lot less boring.
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?
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.
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.
Very well done Node team!
This lets you build simple web apps (i.e., those with no frontend dependencies) as pure TypeScript, including the frontend, by stripping the types out from your frontend scripts as you serve them: https://github.com/bakkot/buildless-ts-webapp
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.)
Except for where it does: Enums, namespaces, parameter properties, 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.
Is this that worth? In the past I was able to read past async/await desugaring generated by TypeScript, and there are several useful non-JS syntaxes that are much easier to read than that (e.g. enums). Of course it would be great if ECMAScript eventually adopts them, but that doesn't seem like a strict requirement for me.
This is true for a number of features, most recently explicit resource management, that required syntax that was part of ECMAScript, supported in typescript, but not yet implemented in mainstream runtimes. In these cases, typescript is doing the same thing as Babel and converting new JS to old.
The syntaxes under discussion are ones that were implemented in typescript without an ECMAScript proposal, and typically are completely non-standard, and may well never be implemented in browsers, or if they are, be implemented completely differently. For example, the original decorators implementation was completely different to the one now being proposed, and enums could well end up going in the same direction.
This confusion about what is typescript, and what is javascript is exactly why I think avoiding the typescript-only features is a good idea. I've seen a lot of people make this mistake (and it's a very reasonable mistake to make!) and I think it's easier to explain what typescript is actually doing if you make it clear that it's only doing type-level stuff.
How useful is it exactly that you accept to not use DX improving syntax like constructor properties, enums, etc? To me, someone who uses these features _a lot_, this would be a terrible trade. Seems more like people push this out of ideology and because TS is never going to be part of node itself (since its implementation is just way too slow)
In terms of the tradeoff, I rarely, if ever, use those features in the first place. For enums, I'd rather just use string literal types, which are slightly easier to write and compose, potentially assigning them to variable names if I think that's clearer for a given use-case. For constructor properties, they don't work for private properties which is the most common property I'm writing, so I don't really see the value.
The two other features that are disabled are namespaces and modules that contain code, and `import ... =`, both of which have clearer JS alternatives.
FWIW, you can enable those features in node, although that's still behind an experimental flag. It's marginally slower (because the translation is slightly more complex than replacing types with whitespace) and it requires sourcemaps under the hood, but if you really want those features you can have them. Or you can use an alternate runtime that supports them out of the box (Deno, Bun). But even then, I think they make teaching and understanding the language more complicated and I wouldn't personally recommend them. (Assuming someone might listen to the personal recommendation of a random HN comment!)
In Python basically everything is executable, and so are type annotations.
This of course requires your build tool to actually understand the TS type system, which is why it's not supported in tools like esbuild and tsx (which uses esbuild under the hood).
Just for generics, I believe
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.
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.
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.
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.
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?
that's exactly the point--GP is pointing out that node can't do that part
I think running TS without type checks is almost entirely pointless.
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.
I suspect this would only handle the most rudimentary and basic typescript files. Once you start using the type system more extensively I suspect this will blow-up in your face.
It's kinda a shame. What a missed opportunity to do it properly (rather than relying on third party plugins etc)
Edit: if you are just using typescript for type checking at build time (i.e. the most basic rudimentary typescript files) the sure fine this may help you. But typescript also generates JavaScript code for missing features e.g. proper class access modifiers, generics, interfaces, type aliases, enums, decorators etc etc. typescript generates JS code to handle all that, so you're going to have a bad time if node just replaces it with the space character at run time.
Source: 49m 43s https://m.youtube.com/watch?v=NrEW7F2WCNA
They know they can skyrocket adoption by limiting the language. That's the reason they regret it. This is just a strategy to increase adoption. Not because they are bad features. They are in fact very useful, and you should not stop using them just because your favorite runtime decided to go the easy way and only supporting a subset of TS by stripping types. You should rather switch the runtime instead of compromising your codebase.
Of course, everyone else is free to use enums, decorators, class parameters, etc., but for quick prototyping or writing simple scripts in TypeScript, Bun has been good enough for me, and I assume Node will be "good enough" as well.
[1] https://www.typescriptlang.org/docs/handbook/release-notes/t...
That’s what all the other tools like ts-node and tsx do already.
I’m not sure what more are you expecting to do?
Typescript is build time type checked, there is no runtime component to TypeScript. If you want type checking you run tsc.
I think this is a great step in the right direction by node. It will save transpiration on the server and improve stack traves and whatnot.
> Once you start using the type system more extensively I suspect this will blow-up in your face.
I don’t see why. There isn’t any more runtime information in “complex” TypeSceipt types than in simple ones. It’s all build time - see above.
> What a missed opportunity to do it properly
Please explain in more detail what “doing it properly” means to you. Including the typechecker? If so that wouldn’t make sense - they would be competing with TypeScript itself, they shouldn’t, all the “third party plugins” rely on tsc for type checking.
I think it's the opposite. It will be a net negative, since people will now run TS by default without type checking. Wasting so much time chasing weird runtime errors - just to end up running the full blown TSC type checking again. They will also write very different TS now, trying to workaround the limitation and arguably very useful features like Enums, constructor properties, etc. This has real negative effects on your codebase if you rely on these, just because Node chose to support only a subset.
It's interesting to see the strategy now and to see people even gaslighting people into believing no type checks and less features is a good thing. All just because of one root cause - TSC being extremely slow.
Not exactly. Typescript enums and decorators are examples.
You’re downplaying this quite a bit. Node being able to execute TS files slashes a lot of tooling in half, especially during development when you want to modify files and retry in quick succession.
Besides, I’m not so sure this cannot be expanded in the future to adopt validation features before stripping the type information. For now it solves some major pain points for a lot of people.
Remember ESM fiasco? Now we have a few years of that all over again, this time with different versions of TypeScript, settings, and tsgo. Good news!
esm actually caused errors so there should not be any issues here
Just converted many of services to TS and some are WIP. It’s a big win.
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.
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!!
This is a really neat idea and I hope typescript adds this as a compiler option.
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.
I’ve always just run tsc to a .gitignored’d directory and execute my JS from there.
Edit: Thanks for the responses. There’s some great examples in there!
As a note, Node has a built in --watch now, too
Fastify, NestJS (bleh), Koa, Hono are the modern replacements for express, none of them have caught on as a standard though. My personal favorite for small projects is Polka (https://github.com/lukeed/polka), when I'm not using Go instead.
And do mean everything: I run an entire Postrgraphile server through Next (and you can easily do the sme with Supabase or a similar tool)!
ES5 standard (released in 2009) didn't have modules.
TypeScript is for compile time checking of a language that was not designed to have them. Runtime types have very different requirements! It has to be in the language from the early design phase, otherwise it will just be a hack with many conditions, restrictions and holes.
TS Types are only partially a description of the underlying types in the code, a very big part instead is that it provides guard rails that prevent you from using a lot of perfectly fine and valid JS code that would however be incompatible with type guarantees. You pay the price of using only a part of the large space of JS code possibilities for guarantees. If you were to put that into the runtime you would end up with two different versions of the language. If you still want to support the full JS you would end with two runtimes in one (or one that has so many branches and conditions that maintaining that runtime is a real beast).
Now of course, this would only add type syntax to the language, not true processing: there's nothing in the spec about actually handling them. Still, it's a step in that direction, so I wouldn't say "very unlikely to ever happen" ... "still a long ways off (if ever)" would be more accurate.
Only downside I see is that It can slow down the code as the runtime now has to evaluate type level functions in order to know what to place in the metadata.
But you're right in the sense that it has to go into the core ECMA specification rather then being a node project.
typescript is pretty ambiguous about a lot of the things that would need explicit definition for runtime safety, and anyways we already have tools for that - it’s called zod.
and comprehensive checks would incur a significant runtime penalty, unless they were restricted to external interfaces, which is what you’re really concerned about. we already have tools for that - protobuf, swagger, etc.
anything else is sharing a runtime with you. so either it’s in your ide, and you just don’t write shitty code; or you’re trapped in some kind of demonic javascript prisoner’s dilemma, and you are mutable.
so typescript is basically ‘good enough’ for developers.
thinking forward anyway, and assuming you’re really willing to share a runtime with a stranger…
node doesn’t really operate in that kind of context, but maybe browser code does. i could imagine a framework based on web components, workers, and maybe iframes, taking advantage of message boundaries to enhance analysis and conceal code generation. it’s not that much better than typescript.
but if you want efficient runtime checks, and you want to leverage static analysis and strong module boundaries to scope the type-checking codegen, and you probably need additional syntax, you might as well target wasm.
Caveat is that there are some restrictions with the compiler and some possible footguns (duplicated declarations bloating code).
all threads saying the truth, js on the server could implement actual ts and not yet another transpiler gets downvoted.
js "experts" think they are smarter because they know ts is just annotations for a linter. they don't even question why that is so and why that sucks.
My mind is actually so impressed right now in the sense that bun is well owned by oven.sh company and what you said makes sense...
Do I make sense? Seriously, I can't explain but feel a little mind blown by what you wrote.