# How to Query Data from the iroh Network: Endpoint, Streams, and ALPN Protocols

> Query data from the iroh network by creating an Endpoint, connecting to a peer, opening a QUIC stream, sending your query, and reading the response. Learn how to query iroh.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: how-to-guide
- Published: 2026-07-04

---

**To query data from the iroh network, create an `Endpoint` using the builder pattern, connect to a remote peer via its `EndpointId` and an agreed-upon ALPN protocol string, open a bidirectional QUIC stream, send your query payload, and read the response bytes.**

Querying data from the iroh network leverages a peer-to-peer, QUIC-based transport layer that operates without centralized servers. The n0-computer/iroh repository provides Rust APIs that abstract NAT traversal, relay fallback, and TLS encryption. This guide walks through the exact sequence of method calls—using `Endpoint::bind`, `Endpoint::connect`, and bidirectional streams—to execute remote queries against the iroh network.

## Initializing the Endpoint

The `Endpoint` is the root object that owns all networking resources, including sockets, TLS configuration, and address-lookup services. In [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs), the `Builder` struct configures these components before binding to an address.

### Binding with Default Presets

Use `Endpoint::bind` with `presets::N0` to create a production-ready endpoint with relay support and DNS discovery enabled.

```rust
use iroh::{Endpoint, endpoint::presets};

let endpoint = Endpoint::bind(presets::N0).await?;

```

This invokes `Builder::bind` (lines 224–260 in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs)), which initializes the underlying QUIC socket, configures the `TlsConfig`, and sets up the address-lookup service.

## Establishing Connections with ALPN

To query data from the iroh network, you must connect to a specific peer using its `EndpointId` and negotiate an Application-Layer Protocol Negotiation (ALPN) string.

### The connect Method

The `Endpoint::connect` method (implemented at lines 250–270 in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs)) resolves the remote address via DNS or relay servers, performs the QUIC handshake, and returns a `Connection`.

```rust
const ALPN: &[u8] = b"iroh-example/query/0";
let conn = endpoint.connect(remote_id, ALPN).await?;

```

Both peers must present matching ALPN strings to accept the connection. The method internally calls `connect_with_opts` to handle transport configuration, relay selection, and TLS setup.

## Opening Streams and Sending Queries

QUIC connections support multiplexed streams. For request-response patterns, open a bidirectional stream.

### Bidirectional Stream Operations

Call `conn.open_bi().await?` to create a `(SendStream, RecvStream)` pair. Write your query payload using `write_all`, then signal end-of-data with `finish()`:

```rust
let (mut send, mut recv) = conn.open_bi().await?;

let query = b"search term";
send.write_all(query).await?;
send.finish()?;

```

## Receiving and Processing Responses

Read the response bytes from the receive stream. In the [`examples/search.rs`](https://github.com/n0-computer/iroh/blob/main/examples/search.rs) implementation, the remote returns a `u64` count representing the number of matches:

```rust
let mut buf = [0u8; 8];
recv.read_exact(&mut buf).await?;
let answer = u64::from_le_bytes(buf);

```

## Implementing the Server Side

To accept queries, implement the `ProtocolHandler` trait from [`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs) and register it with a `Router`.

```rust
use iroh::protocol::{ProtocolHandler, Router};
use std::sync::Arc;

struct QueryHandler;

#[async_trait::async_trait]
impl ProtocolHandler for QueryHandler {
    async fn accept(&self, conn: iroh::connection::Connection) -> Result<(), iroh::protocol::AcceptError> {
        let (mut send, mut recv) = conn.accept_bi().await?;
        let data = recv.read_to_end(1024).await?;
        
        // Process query and respond
        let result = data.len() as u64;
        send.write_all(&result.to_le_bytes()).await?;
        send.finish()?;
        
        conn.closed().await;
        Ok(())
    }
}

// Register the handler
let router = Router::builder(endpoint.clone())
    .accept(ALPN.to_vec(), Arc::new(QueryHandler))
    .spawn()
    .await?;

```

## Complete Working Example

Below is a self-contained example demonstrating both client and server sides:

```rust
use iroh::{Endpoint, endpoint::presets, EndpointId};
use iroh::protocol::{ProtocolHandler, Router};
use n0_error::Result;
use tokio::io::AsyncWriteExt;
use std::sync::Arc;

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

#[tokio::main]
async fn main() -> Result<()> {
    // Initialize endpoint
    let endpoint = Endpoint::bind(presets::N0).await?;
    
    // Setup protocol handler
    let router = Router::builder(endpoint.clone())
        .accept(ALPN.to_vec(), Arc::new(EchoHandler))
        .spawn().await?;
    
    // Client side: connect and query
    let remote_id: EndpointId = /* obtain from peer */;
    let conn = endpoint.connect(remote_id, ALPN).await?;
    let (mut send, mut recv) = conn.open_bi().await?;
    
    send.write_all(b"hello world").await?;
    send.finish()?;
    
    let mut buf = [0u8; 8];
    recv.read_exact(&mut buf).await?;
    println!("Response: {}", u64::from_le_bytes(buf));
    
    router.shutdown().await?;
    Ok(())
}

struct EchoHandler;

#[async_trait::async_trait]
impl ProtocolHandler for EchoHandler {
    async fn accept(&self, conn: iroh::connection::Connection) -> Result<(), iroh::protocol::AcceptError> {
        let (mut send, mut recv) = conn.accept_bi().await?;
        let data = recv.read_to_end(1024).await?;
        send.write_all(&(data.len() as u64).to_le_bytes()).await?;
        send.finish()?;
        conn.closed().await;
        Ok(())
    }
}

```

## Key Source Files and Components

Understanding these core files helps when debugging or extending query functionality:

- **[`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs)**: Contains `Endpoint`, `Builder`, and the `connect` method (lines 250–270) that manages peer resolution and QUIC handshakes.
- **[`iroh/src/protocol.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/protocol.rs)**: Defines `ProtocolHandler` and `Router` for registering application-level protocols.
- **[`examples/search.rs`](https://github.com/n0-computer/iroh/blob/main/examples/search.rs)**: Complete implementation of a text-search protocol demonstrating both query client and responder patterns.
- **[`iroh/src/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/connection.rs)**: Provides `Connection::open_bi` and `Connection::accept_bi` for stream management.

## Summary

- **Create an Endpoint** using `Endpoint::bind(presets::N0)` to initialize networking resources.
- **Dial remote peers** with `Endpoint::connect(remote_id, ALPN)`, which handles NAT traversal via relays and address lookup.
- **Open bidirectional streams** using `conn.open_bi()` to send queries and receive responses.
- **Signal EOF** with `send.finish()` to indicate the query payload is complete.
- **Implement ProtocolHandler** to accept incoming queries on the server side using `Router::builder`.

## Frequently Asked Questions

### What is the role of ALPN when querying data from the iroh network?

The ALPN (Application-Layer Protocol Negotiation) string identifies the specific protocol version both peers expect to speak. When you call `Endpoint::connect`, the iroh library attempts to negotiate this protocol with the remote endpoint. If the remote peer has not registered a handler for that ALPN via `Router::accept`, the connection fails. This allows multiple protocols to coexist on the same endpoint.

### How does iroh resolve EndpointId addresses behind NAT?

The `Endpoint::connect` method in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) invokes the address-lookup module to resolve an `EndpointId` into reachable addresses. It attempts direct connections first, then falls back to configured relay servers (specified in `RelayMode` during the build phase) to traverse NAT devices. This process is transparent to the application code.

### Can I use unidirectional streams instead of bidirectional for queries?

Yes. While `open_bi()` creates a bidirectional stream suitable for request-response patterns, you can use `open_uni()` for one-way data transmission. However, for standard query-response workflows, bidirectional streams are recommended because they keep the request and response logically paired on the same QUIC stream ID.

### How do I handle large data responses when querying the iroh network?

For large payloads, the remote endpoint should return content hashes (using the `iroh-blobs` crate) rather than raw bytes. The client can then fetch these blobs using the content-addressing system. This approach avoids blocking the QUIC stream with large transfers and allows for parallel downloads and verification.