Exploring QUIC and HTTP/3: Build and Test a Node.js QUIC Server

This article explains what QUIC is, outlines HTTP/3’s advantages over HTTP/2, and provides step‑by‑step instructions with code samples for compiling Node.js’s QUIC implementation and running a simple QUIC server and client to observe protocol behavior.

Node Underground
Node Underground
Node Underground
Exploring QUIC and HTTP/3: Build and Test a Node.js QUIC Server

When opening Chrome DevTools and inspecting the Network tab, you may encounter protocols beyond HTTP/1.1 and HTTP/2, such as HTTP/2 + QUIC/43.

What is QUIC?

QUIC was originally designed by Jim Roskind at Google in 2012 as a UDP‑based protocol for HTTP traffic. It was submitted to the IETF in 2015, and the IETF QUIC Working Group positioned QUIC as a transport‑layer protocol supporting multiple application protocols, separating the HTTP‑over‑QUIC mapping. In November 2018, the IETF renamed HTTP‑over‑QUIC to HTTP/3.

Key advantages of HTTP/3 over HTTP/2 include:

UDP‑based head‑of‑line blocking reduction; while HTTP/2 mitigates this with multiplexing, TCP’s ordered delivery still causes severe blocking in lossy networks.

Connection migration capability, allowing sessions to persist across IP or network‑interface changes (e.g., Wi‑Fi ↔ cellular).

Reduced round‑trip time (RTT) thanks to QUIC’s design.

User‑space flow‑control enabling easier protocol upgrades, avoiding the costly kernel‑level changes required by TCP.

HTTP/3 is still in draft (draft‑22) and not widely implemented; browsers do not yet support it. Google’s QUIC implementation (GQUIC) aligns with IETF draft‑22 but does not support IETF HTTP‑over‑QUIC. Wireshark may label YouTube’s HTTP/2 + QUIC/43 traffic as GQUIC rather than standard IETF QUIC.

Node.js’s official HTTP/3 support is still at the stage of implementing the QUIC transport layer. You can try compiling nodejs/quic yourself:

git clone https://github.com/nodejs/quic.git
cd quic
./configure
make -j4
# Check the built Node.js version
./out/Release/node --version
# Expected output: v13.0.0-pre

Write your test code (ensure you have a certificate and key, e.g., generated with OpenSSL):

// Prepare certificate and key files, e.g.:
// openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' \
//   -keyout localhost-privkey.pem -out localhost-cert.pem

const { createSocket } = require('quic');
const fs = require('fs');
const key = fs.readFileSync('localhost-privkey.pem');
const cert = fs.readFileSync('localhost-cert.pem');
const debuglog = require('util').debuglog;
const debug = debuglog('7n');

const ServerPort = 12265;
const ClientPort = 12266;

const server = createSocket({ port: ServerPort, type: 'udp4' });
server.listen({ key, cert });
server.on('session', session => {
  debug('QuicServerSession created');
  session.on('secure', () => {
    debug('QuicServerSession TLS Handshake Completed.');
  });
  session.on('stream', stream => {
    debug('Client Stream Received.');
    debug(`Client-initiated stream id: ${stream.id}; bidirectional: ${stream.bidirectional}, unidirectional: ${stream.unidirectional}`);
    stream.on('end', () => {
      console.log(`~~~ Client-initiated stream id: ${stream.id} on end`);
    });
    stream.on('data', data => {
      console.log(`~~~ Client-initiated stream id: ${stream.id} on data: ${data}`);
    });
  });
});
server.on('ready', () => { debug('Server Ready'); });
server.on('listening', () => {
  debug('Server listening on port %d', server.address().port);
  const client = createSocket({ port: ClientPort, type: 'udp4' });
  const req = client.connect({
    type: 'udp4',
    address: 'localhost',
    port: ServerPort,
    rejectUnauthorized: false,
    maxStreamsUni: 10,
  });
  req.on('secure', () => {
    debug('QuicClientSession TLS Handshake Completed');
    const bidi = req.openStream({ halfOpen: false });
    bidi.end('Hello World');
  });
});

Run the script:

./out/Release/node 7n.js
7N 57222: Server Ready
7N 57222: Server listening on port 12265
7N 57222: QuicServerSession created
7N 57222: QuicClientSession TLS Handshake Completed
7N 57222: QuicServerSession TLS Handshake Completed.
7N 57222: Client Stream Received.
7N 57222: Client-initiated stream id: 0; bidirectional: true, unidirectional: false
~~~ Client-initiated stream id: 0 on data: Hello World
~~~ Client-initiated stream id: 0 on end

Analyzing the traffic with Wireshark shows the IETF QUIC protocol being recognized.

Compared to Node.js’s own QUIC implementation, the third‑party library quicker offers higher completeness, implementing QUIC, HTTP/3, and QPACK, allowing HTTP‑style requests (currently only GET). However, it runs on a custom Node.js runtime (Docker image provided) and will require effort to keep up with future Node.js upgrades. Once Node.js officially ships QUIC support, HTTP/3 clients built on top will advance more rapidly.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Node.jsQUICHTTP/3Network Protocol
Node Underground
Written by

Node Underground

No language is immortal—Node.js isn’t either—but thoughtful reflection is priceless. This underground community for Node.js enthusiasts was started by Taobao’s Front‑End Team (FED) to share our original insights and viewpoints from working with Node.js. Follow us. BTW, we’re hiring.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.