<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Masaru Yamagishi</title>
    <description>The latest articles on Forem by Masaru Yamagishi (@yamayuski).</description>
    <link>https://forem.com/yamayuski</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3465523%2F80499a9e-96e3-4234-9603-1d8974050e34.png</url>
      <title>Forem: Masaru Yamagishi</title>
      <link>https://forem.com/yamayuski</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/yamayuski"/>
    <language>en</language>
    <item>
      <title>Try to test deno 2.2+ WebTransport(unstable)</title>
      <dc:creator>Masaru Yamagishi</dc:creator>
      <pubDate>Thu, 28 Aug 2025 15:05:37 +0000</pubDate>
      <link>https://forem.com/yamayuski/try-to-test-deno-22-webtransportunstable-dnc</link>
      <guid>https://forem.com/yamayuski/try-to-test-deno-22-webtransportunstable-dnc</guid>
      <description>&lt;p&gt;&lt;strong&gt;This article is originally published on August 24, 2025 at &lt;a href="https://zenn.dev/yamayuski/articles/48e829862ff48f" rel="noopener noreferrer"&gt;Zenn(Japanese)&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is WebTransport?
&lt;/h2&gt;

&lt;p&gt;WebTransport is a Web API that enables bidirectional communication using UDP as its foundation over HTTP/3 (QUIC), which has seen growing adoption.&lt;/p&gt;

&lt;p&gt;While browser implementations are becoming relatively stable (e.g., Safari from version 26 onward), until recently the server-side options were limited to languages like C++, Rust, Go, or Python.&lt;/p&gt;

&lt;p&gt;With OpenSSL versions now around 3.5, the crypto features required by QUIC are becoming widely available, improving server-side support. Node.js is also slated to support this in v25, expected around October 2025—though other implementations have relied on other SSL libraries like BoringSSL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nodejs/node/pull/59249" rel="noopener noreferrer"&gt;https://github.com/nodejs/node/pull/59249&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Meanwhile, Deno introduced &lt;a href="https://deno.com/blog/v2.2#webtransport-and-quic-apis" rel="noopener noreferrer"&gt;an experimental WebTransport API in version 2.2&lt;/a&gt;, which is the context for this article.&lt;/p&gt;

&lt;p&gt;Incidentally, I’ve been following WebTransport since its initial draft stage and have experimented with it a few years back.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://zenn.dev/yamayuski/scraps/1f2bc1588f422d" rel="noopener noreferrer"&gt;https://zenn.dev/yamayuski/scraps/1f2bc1588f422d&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://zenn.dev/yamayuski/scraps/f923ef3e776c13" rel="noopener noreferrer"&gt;https://zenn.dev/yamayuski/scraps/f923ef3e776c13&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Deno?
&lt;/h2&gt;

&lt;p&gt;Deno is a server-side TypeScript/JavaScript runtime—one alternative to Node.js—written in Rust.&lt;/p&gt;

&lt;p&gt;One of the key differences from Node.js is explicit permission control: by default, Deno cannot access files or network unless those permissions are granted via command-line flags, such as &lt;code&gt;--allow-read=sample.json&lt;/code&gt;. In interactive mode, Deno even prompts you to allow required permissions on the fly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying Deno + WebTransport Together
&lt;/h2&gt;

&lt;p&gt;So I combined the two: I built a WebTransport server using TypeScript in Deno.&lt;/p&gt;

&lt;p&gt;Here’s the sample code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yamayuski/deno-webtransport-sample" rel="noopener noreferrer"&gt;https://github.com/yamayuski/deno-webtransport-sample&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the experiment, I ran Deno v2.4.5 on WSL2 (Ubuntu 24.04). It took me two full days to write the sample, during which I encountered and noted several sticking points—listed below for reference.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. WSL2 Does Not Forward UDP by Default
&lt;/h3&gt;

&lt;p&gt;WSL2 automatically forwards ports bound to &lt;code&gt;127.0.0.1&lt;/code&gt; to the Windows side, which is convenient—but only for TCP. If you need to access the WSL server from another machine, you must set up port forwarding manually in Windows and open the firewall.&lt;/p&gt;

&lt;p&gt;It turns out that WSL2 doesn’t forward UDP-bound ports by default, unlike TCP, which is what most servers listen on—so that behavior is easy to overlook.&lt;/p&gt;

&lt;p&gt;You can workaround this by setting &lt;code&gt;networkingMode=mirrored&lt;/code&gt; in &lt;code&gt;.wslconfig&lt;/code&gt; (default is NAT). Conveniently, the newer “WSL Settings” app lets you adjust this via a GUI. Installing PowerToys also lets you tweak environment variables and hosts via GUI.&lt;/p&gt;

&lt;p&gt;I discovered this issue when trying to connect a Deno server and client via WebTransport inside WSL. It worked fine once forwarding was correctly configured.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/denoland/deno/blob/main/tests/specs/run/webtransport/main.ts" rel="noopener noreferrer"&gt;https://github.com/denoland/deno/blob/main/tests/specs/run/webtransport/main.ts&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Certificate Generation Might Need Explicit &lt;code&gt;127.0.0.1&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Although certificates are usually issued for domains, they can also be issued for IP addresses.&lt;/p&gt;

&lt;p&gt;If you set the hostname to &lt;code&gt;localhost&lt;/code&gt;, it might still bind to &lt;code&gt;127.0.0.1&lt;/code&gt;. So to be safe, generate a certificate that explicitly includes both &lt;code&gt;localhost&lt;/code&gt; and &lt;code&gt;127.0.0.1&lt;/code&gt;, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;mkcert &lt;span class="nt"&gt;-cert-file&lt;/span&gt; localhost.crt &lt;span class="nt"&gt;-key-file&lt;/span&gt; localhost.key localhost 127.0.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/denoland/deno/tree/main/tests/testdata/tls" rel="noopener noreferrer"&gt;https://github.com/denoland/deno/tree/main/tests/testdata/tls&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Behavior may vary depending on whether you’re using WSL or Docker, so take note. In public environments, using Let’s Encrypt certificates avoids these complications.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Deno Alone Lacks Built-In TypeScript Serving
&lt;/h3&gt;

&lt;p&gt;While it’s easy to spin up an HTTP server with &lt;code&gt;Deno.serve&lt;/code&gt;, there doesn’t seem to be a built-in API for serving entire directories or transpiling TypeScript into JavaScript on the fly.&lt;/p&gt;

&lt;p&gt;Older versions may have offered &lt;code&gt;Deno.emit&lt;/code&gt;, but it’s no longer available.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;deno-vite-plugin&lt;/code&gt; enables running a Vite server via &lt;code&gt;deno run&lt;/code&gt;, but serving over HTTPS requires Node.js’s &lt;code&gt;createSecureServer&lt;/code&gt; from &lt;code&gt;http2&lt;/code&gt;, which Deno hasn’t implemented yet. So, using Vite didn’t work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/denoland/deno/blob/v2.4.5/ext/node/polyfills/http2.ts#L1756" rel="noopener noreferrer"&gt;https://github.com/denoland/deno/blob/v2.4.5/ext/node/polyfills/http2.ts#L1756&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You could use &lt;code&gt;local-ssl-proxy&lt;/code&gt; as a workaround, but that felt too clunky—so I ended up embedding all JavaScript directly into &lt;code&gt;index.html&lt;/code&gt;. Since this is just a sample, that’s fine.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Chrome Requires a Flag for Self-Signed Certificates
&lt;/h3&gt;

&lt;p&gt;In Chrome, enable the “WebTransport Developer Mode” flag (&lt;code&gt;chrome://flags/#webtransport-developer-mode&lt;/code&gt;) to allow WebTransport over HTTP/3 with certificates not issued by a known certificate authority.&lt;/p&gt;

&lt;p&gt;Otherwise, self-signed certificates created using mkcert will be rejected. There used to be a way to pass hashes via launch flags, but that didn’t work for me. Enabling the Developer Mode flag was the solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Serving with QUIC is Straightforward
&lt;/h3&gt;

&lt;p&gt;Here’s how I set up a basic HTTP/3 (QUIC) server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;localhost&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4433&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readTextFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;localhost.crt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readTextFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;localhost.key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;QuicEndpoint&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;listener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;alpnProtocols&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;h3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates an HTTP/3 server on &lt;code&gt;127.0.0.1:4433/udp&lt;/code&gt;. While &lt;code&gt;Deno.serve&lt;/code&gt; can handle HTTP/2 or HTTP/1.1, for QUIC you must use the unstable API and include the &lt;code&gt;--unstable-net&lt;/code&gt; flag; otherwise Deno won’t even construct the &lt;code&gt;WebTransport&lt;/code&gt; class.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Firefox Didn’t Work—Promise Rejected
&lt;/h3&gt;

&lt;p&gt;I only tested Chrome thoroughly. When I attempted to connect via Firefox, the promise was rejected, and I couldn’t figure out why—so I left it at that.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Communication Is Primarily Stream-Based
&lt;/h3&gt;

&lt;p&gt;Previously, with WebSocket, bidirectional communication was event-driven:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wss://localhost:8080&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;received&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple and JavaScript-like.&lt;/p&gt;

&lt;p&gt;In contrast, WebTransport uses streams, which is a bit more complex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebTransport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://localhost:4433&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;wt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;writable&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;wt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createBidirectionalStream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;writable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getWriter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello server!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;releaseLock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;readable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextDecoder&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;releaseLock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;wt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the client side, it’s straightforward: create a stream, send data, read the response, and close it—streams are disposable.&lt;/p&gt;

&lt;p&gt;On the server side, you use a new style:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;wt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;writable&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;wt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;incomingBidirectionalStreams&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;readable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipeThrough&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextDecoderStream&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Received&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;writable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getWriter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;textEncoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Pong: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;releaseLock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;wt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;closed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perhaps better use of &lt;code&gt;pipe()&lt;/code&gt; would be more elegant, but this achieves a basic ping-pong example.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/for-await...of" rel="noopener noreferrer"&gt;https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/for-await...of&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, &lt;code&gt;for await&lt;/code&gt; is relatively new syntax. It works with &lt;code&gt;AsyncIterator&lt;/code&gt; to process items sequentially.&lt;/p&gt;

&lt;p&gt;Interestingly, PHP is also gravitating toward &lt;code&gt;: iterable&lt;/code&gt; instead of &lt;code&gt;: array&lt;/code&gt;, using &lt;code&gt;yield $val1;&lt;/code&gt; constructs for iterators (useful in PHPUnit DataProviders)—though that’s a bit of a tangent.&lt;/p&gt;

&lt;p&gt;Note that WebTransport streams can time out quickly if unused, requiring client-side reconnection via a new stream.&lt;/p&gt;

&lt;p&gt;Moreover, although this communication uses UDP, it provides TCP-like reliability (minus guaranteed order—applications must handle that). With &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/datagrams" rel="noopener noreferrer"&gt;wt.datagrams&lt;/a&gt;, you can also perform truly unreliable UDP-like communication—no retransmission or ordering—useful in scenarios like live streaming, gaming, or video calls.&lt;/p&gt;

&lt;p&gt;This dual capability enables the developing &lt;a href="https://datatracker.ietf.org/group/moq/about/" rel="noopener noreferrer"&gt;“Media over QUIC (moq)”&lt;/a&gt; spec, gaining attention as a potential alternative to WebRTC or custom UDP implementations (RUDP).&lt;/p&gt;




&lt;p&gt;In summary, I tried using Deno to implement WebTransport communication. Stream-based communication libraries are still scarce—so if you’re working on this, you could be a pioneer!&lt;/p&gt;

&lt;p&gt;—やまゆ (Web Engineer with Laravel, babylon.js, AWS)&lt;/p&gt;

&lt;p&gt;CAUSION: This article was translated using ChatGPT!&lt;/p&gt;

</description>
      <category>deno</category>
      <category>webtransport</category>
      <category>webdev</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
