Fetch and HTTP/2 support in Node.js, Bun and Deno
Recently at Speakeasy, we received a report that a user was unable to hit an HTTP/2 endpoint from the TypeScript SDK they generated with our code generator. I was a little surprised because I didn’t think this was going to be a problem. Our SDKs are built using the Web Fetch API and avoid using any non-standard polyfills like node-fetch
or similar packages. The Fetch Standard is very much written with HTTP/2 in mind and it’s easy to think this would be table stakes. On the popular, everygreen browsers, you can open the network tab in developer tools on many sites that use fetch
for API calls and often observe these happening over HTTP/2.
Of course, the situation with backend JavaScript is rather different. With a small test setup I found that Node.js, Bun and Deno have varying support which may either be opt-in or outright unavailable.
The test setup
Probably the strangest thing from the user’s bug report was that the server they were connecting to did not allow HTTP/1.1 clients. This was a novel setup since many server frameworks, proxies and CDNs seamlessly support HTTP/2 with backwards compatibility for HTTP/1.1. It’s not something you think about unless perhaps you’re running a file upload or streaming service where things like request body streaming are central concerns.
In order to reproduce the issue, I decided to create a Golang server with a simple handler that responded with an error when it received requests from HTTP/1.1 clients. I also used mkcert to generate a self-signed certificate for the server.
I confirmed that HTTP/2 was setup correctly with curl
:
Finally, I created a simple script that I can run from Node.js, Bun and Deno:
Results
Node.js
Tested with Node.js v22.13.0
The Fetch API is supported in Node.js through Undici, the official HTTP client that is built into Node.js since at least Node.js v18. Out of the box, Node.js throws an error:
There is a workaround for this which is to install undici
as a dependency which exposes an option to enable HTTP/2 support:
The modified script will run to completion and log Hello, world!
to the console.
Deno
Tested with Deno v2.1.6
Great news here! fetch
in Deno natively supports HTTP/2 and running the original script worked seamlessly.
Bun
Tested with Bun v1.1.45
Sadly, Bun does not appear to support HTTP/2 with its fetch implementation at this time and I don’t believe there are great workarounds at this time. I tried testing it with undici
without success. There is however an open issue opened by the Jarred, creator of Bun, to add HTTP/2 support.
A moment to grieve
As part of building a TypeScript SDK generator for my customers and their users, I spend a considerable amount of time surveying interoperability of JavaScript features across the browsers and backend runtimes. A few years ago, we could get away with writing Javascript/TypeScript code targetted squarely at Node.js and so if I’m building a library that made HTTP requests, I would solve my problem by installing Undici and configuring a client that enabled HTTP/2. Nowadays, the rising popularity of alternative runtimes makes it important to find a common ground between them if your goal is to build truly portable libraries. Despite the discrepencies, using platform APIs like the Fetch API is still your best bet for maximum interoperability. This is at least true if you intend for your code to also run on the browser.
For some time now, I’ve been optimistic about WinterTC, an initiative that aims to formally define interoperability across server-side JavaScript runtimes. One of the goals is to properly define the requirements for the Fetch API on the server and many runtime authors have backed it.
Conclusion
This particular example where a server is rejecting HTTP/1.1 clients is rather uncommon but it did present an opportunity to dig deeper into popular runtimes and test their internals. Web platform APIs present a great and positive incentive for all runtimes to get behind because it’s a bridge for developers to migrate between them and to reach more users in the JS ecosystem.