A Go library for building peer-to-peer systems over QUIC. Provides connection pooling, stream multiplexing, and unreliable datagram support with a route-based handler API. Handles the transport layer so you can focus on application logic.
- Identity: No public keys, peer IDs, or cryptographic identity
- Discovery: No DHT, mDNS, or peer discovery mechanisms
- Decentralization: No blockchain, consensus, or distributed hash tables
This is a transport library. Bring your own identity layer and discovery mechanism.
Stream Request:
┌──────────────────┬─────────────────┬──────────────────┬──────────────┐
│ Route Length (2) │ Route (variable)│ Payload Len (4) │ Payload │
└──────────────────┴─────────────────┴──────────────────┴──────────────┘
Stream Response:
┌──────────────────┬──────────────┐
│ Payload Len (4) │ Payload │
└──────────────────┴──────────────┘
┌──────────────────┬─────────────────┬──────────────┐
│ Route Length (1) │ Route (variable)│ Payload │
└──────────────────┴─────────────────┴──────────────┘
All integers are big-endian. Route is UTF-8. Payload is opaque bytes.
go get github.com/Dishank-Sen/quicnodeRequires Go 1.25+
package main
import (
"context"
"fmt"
"log"
"github.com/Dishank-Sen/quicnode/node"
"github.com/quic-go/quic-go"
)
func main() {
ctx := context.Background()
// Node A - listens and handles requests
// TLS certificates automatically derived from node's keypair
nodeA, _ := node.NewNode(ctx, node.Config{
ListenAddr: "127.0.0.1:4242",
QuicConfig: &quic.Config{},
})
nodeA.Start()
nodeA.HandleStream("echo", func(c node.StreamContext) {
log.Printf("Received: %s", string(c.Payload()))
c.Write([]byte("echo: " + string(c.Payload())))
})
// Node B - connects and sends request
nodeB, _ := node.NewNode(ctx, node.Config{
ListenAddr: "127.0.0.1:4243",
QuicConfig: &quic.Config{},
})
nodeB.Start()
peer, _ := nodeB.OpenConn(ctx, "127.0.0.1:4242")
respCh, _ := peer.Send("echo", []byte("hello"))
response := <-respCh
fmt.Printf("Got: %s\n", string(response))
}func NewNode(ctx context.Context, cfg Config) (*Node, error)Creates a new node. Context controls lifetime. Returns error if config is invalid.
func (n *Node) Start() errorStarts listening for connections. Non-blocking. Returns error if listener fails.
func (n *Node) Stop() errorGracefully shuts down the node and closes all connections.
func (n *Node) Wait() errorBlocks until node context is cancelled or an error occurs.
func (n *Node) HandleStream(route string, h StreamHandlerFunc)Registers a handler for reliable stream-based requests on the given route.
func (n *Node) HandleDatagram(route string, h DatagramHandlerFunc)Registers a handler for unreliable datagram-based requests on the given route.
func (n *Node) OpenConn(ctx context.Context, addr string) (*Peer, error)Opens a connection to the remote address. Reuses existing connection if available.
func (n *Node) Events() <-chan types.EventReturns channel of connection lifecycle events (opened, closed).
func (p *Peer) Send(route string, payload []byte) (<-chan []byte, error)Sends a stream-based request. Returns channel that receives response chunks. Reliable, ordered.
func (p *Peer) SendDatagram(route string, payload []byte) errorSends an unreliable datagram. Fire-and-forget. No response channel. May be lost or reordered.
func (p *Peer) Close()Closes the connection to this peer.
Route() stringReturns the route string from the request.
Payload() []byteReturns the request payload bytes.
PeerAddr() stringReturns the remote peer's address.
Write([]byte) (int, error)Writes response data back to the requester. Can be called multiple times.
Route() stringReturns the route string from the datagram.
Payload() []byteReturns the datagram payload bytes.
PeerAddr() stringReturns the remote peer's address.
Note: No Write method. Datagrams are one-way.
type Config struct {
ListenAddr string // IP:port to bind to
TlsConfig *tls.Config // TLS configuration (required)
QuicConfig *quic.Config // QUIC transport config (required)
}For datagrams, set quicConfig.EnableDatagrams = true.
Use streams when:
- Data must arrive (financial transactions, commands)
- You need a response
- Order matters
- Payload is large (>1KB)
Example:
// Server
node.HandleStream("transfer", func(ctx node.StreamContext) {
amount := parseAmount(ctx.Payload())
result := processTransfer(amount)
ctx.Write(result)
})
// Client
peer, _ := node.OpenConn(ctx, addr)
respCh, _ := peer.Send("transfer", []byte("100"))
result := <-respChCharacteristics:
- Guaranteed delivery
- In-order arrival
- Retransmission on loss
- ~5-50ms latency
- Request-response pattern
Use datagrams when:
- Loss is acceptable (telemetry, game updates)
- Low latency is critical (<1ms)
- Data is frequent and redundant
- Payload is small (<1KB)
Example:
// Server
node.HandleDatagram("position", func(ctx node.DatagramContext) {
x, y, z := parsePosition(ctx.Payload())
updatePlayerPosition(ctx.PeerAddr(), x, y, z)
// No response - one-way only
})
// Client (game loop at 60 FPS)
peer, _ := node.OpenConn(ctx, addr)
for range time.Tick(16 * time.Millisecond) {
peer.SendDatagram("position", serializePosition(player))
}Characteristics:
- May be lost (95-99% delivery typical)
- May arrive out of order
- No retransmission
- <1ms latency
- One-way only (no response)
┌─────────────────────────────────────────┐
│ Application │
│ (Your handlers & business logic) │
└──────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ node.Node │
│ • Listen for connections │
│ • Route registration │
│ • Connection pooling │
└──────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ connection.Peer │
│ • Per-peer connection handle │
│ • Stream multiplexing │
│ • Datagram sending │
└──────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ stream.Stream │
│ • Parse incoming requests │
│ • Handler dispatch │
│ • Response serialization │
└──────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ parser.Parser │
│ • Binary framing (route + payload) │
│ • Length-prefix encoding │
│ • Big-endian integers │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ quic-go/quic-go │
│ • QUIC protocol implementation │
│ • UDP transport │
└─────────────────────────────────────────┘
Incoming requests flow bottom-up. Outgoing requests flow top-down. Connection manager pools connections by remote address and reuses existing connections when available.