How to Get Started with Iroh Examples: A Complete Guide for Beginners

To get started with Iroh examples, clone the n0-computer/iroh repository and run the echo, blob transfer, or gossip examples using cargo run -p iroh --example <name>, which demonstrate the core Endpoint and Router APIs for peer-to-peer QUIC connections.

Iroh is a Rust-first library that enables applications to dial peers by public key, automatically selecting the fastest path through direct connections, hole-punching, or public relays. This guide covers the essential beginner examples from the n0-computer/iroh repository, showing you how to establish connections, transfer content-addressed data, and build publish-subscribe networks.

Prerequisites and Repository Setup

Before running the examples, ensure you have Rust installed. Clone the repository and explore the available crates:

git clone https://github.com/n0-computer/iroh.git
cd iroh

The repository contains several key crates:

Example 1: Simple Echo Protocol

The echo example demonstrates the fundamental pattern of connecting two peers over QUIC using Application-Layer Protocol Negotiation (ALPN). This example uses the Endpoint type from [iroh/src/lib.rs](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs) to manage connections.

Client Implementation

Create a client that connects to a remote peer and sends a message:

use iroh::Endpoint;
use anyhow::Result;
use tokio::io::{AsyncWriteExt, AsyncReadExt};

const ALPN: &[u8] = b"iroh-example/echo/0";

#[tokio::main]
async fn main() -> Result<()> {
    // Bind a local endpoint that manages connections
    let endpoint = Endpoint::bind().await?;
    
    // Connect to a remote peer using their address
    let addr = "...remote endpoint address...".parse()?;
    let conn = endpoint.connect(addr, ALPN).await?;
    
    // Open a bidirectional QUIC stream
    let (mut send, mut recv) = conn.open_bi().await?;
    
    // Send payload and read response
    send.write_all(b"Hello, iroh!").await?;
    send.finish()?;
    let mut response = Vec::new();
    recv.read_to_end(&mut response).await?;
    
    assert_eq!(response, b"Hello, iroh!");
    
    // Cleanup
    conn.close(0u32.into(), b"bye!");
    endpoint.close().await;
    Ok(())
}

The Endpoint::bind() method creates a local endpoint that owns a QUIC engine, while connect() handles DNS resolution, hole-punching, and relay fallback automatically.

Server Implementation

The server side implements the ProtocolHandler trait to accept incoming connections:

use iroh::Endpoint;
use iroh::protocol::ProtocolHandler;
use iroh::connection::Connection;
use anyhow::Result;
use std::sync::Arc;

const ALPN: &[u8] = b"iroh-example/echo/0";

#[derive(Debug, Clone)]
struct Echo;

#[async_trait::async_trait]
impl ProtocolHandler for Echo {
    async fn accept(&self, conn: Connection) -> Result<()> {
        let (mut send, mut recv) = conn.accept_bi().await?;
        tokio::io::copy(&mut recv, &mut send).await?;
        send.finish()?;
        conn.closed().await;
        Ok(())
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    let endpoint = Endpoint::bind().await?;
    let router = Router::builder(endpoint)
        .accept(ALPN.to_vec(), Arc::new(Echo))
        .spawn()
        .await?;
    // Router runs indefinitely
    Ok(())
}

The Router::builder registers protocol handlers by ALPN identifier, allowing the same endpoint to serve multiple protocols.

Example 2: Blob Transfer with iroh-blobs

For transferring large files, iroh-blobs provides a content-addressed store using BLAKE3 hashes. This example demonstrates the high-level API similar to the echo pattern but with persistent storage.

use iroh::Endpoint;
use iroh_blobs::BlobStore;
use anyhow::Result;
use std::path::PathBuf;

const ALPN: &[u8] = b"iroh-blobs/transfer/0";

#[tokio::main]
async fn main() -> Result<()> {
    // Initialize endpoint and on-disk blob store
    let endpoint = Endpoint::bind().await?;
    let store = BlobStore::open(PathBuf::from("./my_blobs")).await?;
    
    // Sender: Add file to store and transmit
    let cid = store.add_file("large_file.dat").await?;
    let conn = endpoint.connect(remote_addr, ALPN).await?;
    store.send(&conn, cid).await?;
    
    // Receiver: Accept connections and automatically store blobs
    let router = Router::builder(endpoint)
        .accept(ALPN.to_vec(), Arc::new(store.clone()))
        .spawn()
        .await?;
    Ok(())
}

Key implementation files include [iroh-blobs/src/lib.rs](https://github.com/n0-computer/iroh/blob/main/iroh-blobs/src/lib.rs) for the core store and iroh-blobs/examples/transfer.rs for the complete runnable demo.

Example 3: Publish-Subscribe with iroh-gossip

The gossip protocol builds a scalable overlay network on top of Iroh's connection layer, enabling message broadcasting to multiple peers.

Publisher Implementation

Create a publisher that sends messages to a topic:

use iroh::Endpoint;
use iroh_gossip::{Gossip, Topic};
use anyhow::Result;
use std::sync::Arc;

#[tokio::main]
async fn main() -> Result<()> {
    let endpoint = Endpoint::bind().await?;
    let gossip = Gossip::new(endpoint.clone()).await?;
    
    // Create topic from arbitrary bytes
    let topic = Topic::from(b"chat-room-1".as_ref());
    
    // Publish messages periodically
    let mut interval = tokio::time::interval(std::time::Duration::from_secs(1));
    loop {
        interval.tick().await;
        let msg = format!("ping at {}", chrono::Utc::now());
        gossip.publish(&topic, msg.into_bytes()).await?;
    }
}

Subscriber Implementation

Subscribers register callbacks to receive messages:

use iroh::Endpoint;
use iroh_gossip::{Gossip, Topic, Message};
use anyhow::Result;

#[tokio::main]
async fn main() -> Result<()> {
    let endpoint = Endpoint::bind().await?;
    let gossip = Gossip::new(endpoint.clone()).await?;
    let topic = Topic::from(b"chat-room-1".as_ref());
    
    gossip.subscribe(&topic, |msg: Message| async move {
        println!("Got: {}", String::from_utf8_lossy(&msg.payload));
    }).await?;
    
    // Keep alive indefinitely
    futures::future::pending::<()>().await;
    Ok(())
}

The high-level API is defined in [iroh-gossip/src/lib.rs](https://github.com/n0-computer/iroh/blob/main/iroh-gossip/src/lib.rs), with a complete example available at iroh-gossip/examples/pubsub.rs.

Running a Local Relay (Optional)

For testing NAT traversal without depending on public infrastructure, run a local relay server:

cargo run -p iroh-relay -- --listen 0.0.0.0:2345 --cert ./cert.pem --key ./key.pem

The relay server implementation resides in [iroh-relay/src/main.rs](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/main.rs), which parses CLI options and starts the QUIC listener, while [iroh-relay/src/lib.rs](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/lib.rs) contains the core client handling and connection pooling logic.

Key Source Files and Architecture

Understanding the repository structure helps when extending these examples:

Summary

  • Clone the repository from n0-computer/iroh to access official examples and source code
  • Start with the echo example to understand Endpoint::bind(), connect(), and the ProtocolHandler trait
  • Use iroh-blobs for content-addressed file transfer with automatic deduplication via BLAKE3 hashing
  • Implement gossip protocols using iroh-gossip for scalable publish-subscribe messaging over the same QUIC backbone
  • Test locally with iroh-relay to understand NAT traversal without external dependencies

Frequently Asked Questions

How do I run the Iroh examples without cloning the entire repository?

You can add the specific crates as dependencies in your Cargo.toml and copy the example code from the repository's examples/ directories. For instance, add iroh = "0.7" and iroh-blobs = "0.7" to your dependencies, then adapt the code from iroh-blobs/examples/transfer.rs to your project structure.

What is the difference between Endpoint and Router in Iroh?

The Endpoint struct, defined in [iroh/src/lib.rs](https://github.com/n0-computer/iroh/blob/main/iroh/src/lib.rs), manages the underlying QUIC connections and dialing logic. The Router allows you to register multiple ProtocolHandler implementations by ALPN identifier, enabling a single endpoint to serve different protocols simultaneously without manual connection dispatch.

Do I need to run my own relay server to use Iroh?

No, Iroh includes default public relays that handle NAT traversal automatically. However, for private deployments or testing offline scenarios, you can run your own relay using the iroh-relay crate by executing cargo run -p iroh-relay with appropriate certificate and listen address parameters.

Which Iroh example should I start with as a beginner?

Begin with the echo example because it demonstrates the fundamental connection lifecycle: binding an endpoint, connecting by address, opening bidirectional streams, and graceful shutdown. Once you understand this pattern, the blob transfer and gossip examples build upon the same Endpoint and Router APIs with additional protocol-specific logic.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →