Quick Start

Three ways to run an HTTP/3 server — pick the one that fits your stack.

Option 1: Python — Serve a FastAPI app

# app.py
from fastapi import FastAPI
import nhttp3

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello from HTTP/3!"}

@app.get("/health")
async def health():
    return {"status": "ok", "protocol": "h3"}

# One line — replaces uvicorn.run(app)
nhttp3.serve(app, port=4433, certfile="cert.pem", keyfile="key.pem")
# Run it
python app.py
# Test it
curl --http3 https://localhost:4433/ -k
curl --http3 https://localhost:4433/health -k

Option 2: CLI — Serve any ASGI app

# Serve an existing app without modifying code
nhttp3 run myapp:app --port 4433 --certfile cert.pem --keyfile key.pem

# Equivalent to:
# uvicorn myapp:app --port 8000

Option 3: Rust — Low-level QUIC

use nhttp3::quic::endpoint::Endpoint;
use nhttp3::quic::config::Config;

let config = Config::default();
let endpoint = Endpoint::bind(
    "0.0.0.0:4433".parse()?,
    config,
    Some(server_tls),
    None,
).await?;

// Accept connections
let conn = endpoint.accept().await.unwrap();
conn.established().await;

// Open a stream
let (send, recv) = conn.open_bidi_stream().unwrap();
send.write_all(b"hello").await?;

What Just Happened?

StepWhat nhttp3 did
1Bound a UDP socket on port 4433
2Started listening for QUIC Initial packets
3Client sent a QUIC Initial with TLS ClientHello inside a CRYPTO frame
4Server completed TLS 1.3 handshake in 1-RTT (vs 2-RTT for TCP+TLS)
5Client opened a bidirectional QUIC stream
6HTTP/3 request/response flowed over that stream with QPACK-compressed headers
Key Difference from HTTP/2
Each request uses its own QUIC stream. If one response is slow, it doesn't block others. This is the "no head-of-line blocking" advantage that HTTP/3 provides over HTTP/2's shared TCP connection.

Next Steps