Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Collecting events

fetch_events runs the same kind of finite request as stream_events, but buffers matching events and returns a deduplicated collection only after the request terminates. It is convenient for a small result set that must be sorted, compared, or passed to an API expecting a collection.

Prefer streaming when events can be processed independently, the response size is uncertain, or latency to the first result matters.

Choose between the two finite request shapes based on the consumer:

RequirementPrefer
Process each event independentlystream_events
Start work before all relays reach EOSEstream_events
Keep memory bounded by downstream capacitystream_events
Sort or compare the complete small result setfetch_events
Pass a collection to an existing APIfetch_events

fetch_events is not more authoritative than a stream. It uses the same relay request and termination model, then materializes the successful events for convenience. The tradeoff is that the caller receives no value until the operation ends and does not retain per-item relay provenance.

Rust
use std::time::Duration;

use nostr_sdk::prelude::*;

async fn fetch_events() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::default();
    client.add_relay("wss://relay.damus.io").await?;
    client.connect().await;

    let filter = Filter::new().kind(Kind::TextNote).limit(20);
    let events = client
        .fetch_events(filter)
        .timeout(Duration::from_secs(10))
        .await?;

    for event in events {
        println!("{}", event.as_json());
    }

    Ok(())
}
Python
import asyncio
from datetime import timedelta

from nostr_sdk import Client, Filter, Kind, KindStandard, RelayUrl, ReqTarget

async def fetch_events() -> None:
    client = Client()
    await client.add_relay(RelayUrl.parse("wss://relay.damus.io"))
    await client.connect()

    filter = Filter().kind(Kind.from_std(KindStandard.TEXT_NOTE)).limit(20)
    events = await client.fetch_events(
        ReqTarget.auto([filter]), timeout=timedelta(seconds=10)
    )

    for event in events:
        print(event.as_json())

JavaScript
Node.js
import {
    Client,
    Filter,
    Kind,
    KindStandard,
    RelayUrl,
    ReqTarget,
} from "@nostrdevkit/nostr-sdk-node";

async function fetchEvents() {
    const client = new Client();
    await client.addRelay(RelayUrl.parse("wss://relay.damus.io"));
    await client.connect();

    const filter = new Filter()
        .kind(Kind.fromStd(KindStandard.TextNote))
        .limit(20n);
    const events = await client.fetchEvents(ReqTarget.auto([filter]), 10_000);

    for (const event of events) {
        console.log(event.asJson());
    }

}
Web
import {
    Client,
    Filter,
    Kind,
    KindStandard,
    RelayUrl,
    ReqTarget,
    uniffiInitAsync,
} from "@nostrdevkit/nostr-sdk-web";

await uniffiInitAsync();

async function fetchEvents() {
    const client = new Client();
    await client.addRelay(RelayUrl.parse("wss://relay.damus.io"));
    await client.connect();

    const filter = new Filter()
        .kind(Kind.fromStd(KindStandard.TextNote))
        .limit(20n);
    const events = await client.fetchEvents(ReqTarget.auto([filter]), 10_000);

    for (const event of events) {
        console.log(event.asJson());
    }

}
React Native
import {
    Client,
    Filter,
    Kind,
    KindStandard,
    RelayUrl,
    ReqTarget,
} from "@nostrdevkit/nostr-sdk-react-native";

export async function fetchEvents() {
    const client = new Client();
    await client.addRelay(RelayUrl.parse("wss://relay.damus.io"));
    await client.connect();

    const filter = new Filter()
        .kind(Kind.fromStd(KindStandard.TextNote))
        .limit(20n);
    const events = await client.fetchEvents(ReqTarget.auto([filter]), 10_000);

    for (const event of events) {
        console.log(event.asJson());
    }

}
Kotlin
import java.time.Duration
import kotlinx.coroutines.runBlocking
import org.nostrdevkit.sdk.*

suspend fun fetchEvents() {
    val client = Client()
    client.addRelay(RelayUrl.parse("wss://relay.damus.io"))
    client.connect()

    val filter = Filter()
        .kind(Kind.fromStd(KindStandard.TEXT_NOTE))
        .limit(20u)
    val events = client.fetchEvents(
        ReqTarget.auto(listOf(filter)),
        timeout = Duration.ofSeconds(10),
    )

    for (event in events) {
        println(event.asJson())
    }

}
Swift
import Foundation
import NostrSDK

func fetchEvents() async throws {
    let client = Client()
    let relay = try RelayUrl.parse(url: "wss://relay.damus.io")
    _ = try await client.addRelay(url: relay)
    await client.connect()

    let filter = Filter()
        .kind(kind: Kind.fromStd(e: .textNote))
        .limit(limit: 20)
    let events = try await client.fetchEvents(
        target: ReqTarget.auto(filters: [filter]),
        timeout: 10.0
    )

    for event in events {
        print(try event.asJson())
    }

}
C#
using Nostr.Sdk;

public static class ReadExample
{

    public static async Task FetchEvents()
    {
        var client = new Client();
        await client.AddRelay(RelayUrl.Parse("wss://relay.damus.io"));
        await client.Connect();

        var filter = new Filter()
            .Kind(Kind.FromStd(KindStandard.TextNote))
            .Limit(20);
        var events = await client.FetchEvents(
            ReqTarget.Auto([filter]),
            timeout: TimeSpan.FromSeconds(10)
        );

        foreach (var @event in events)
        {
            Console.WriteLine(@event.AsJson());
        }

    }
}

The filter and timeout are the same as in the stream example. Only the consuming shape changes: this call waits for termination and returns a collection.

Bound the collection

The filter limit is applied by each relay, not once to the merged collection. Use a selective filter, a timeout, and a global event cap when a strict collection bound is required.

The SDK validates and deduplicates returned events by ID. Replaceable events and deletions still follow database and NIP semantics rather than simple ID deduplication.

Fetch errors that prevent target resolution or request creation fail the operation. Relay-specific behavior can still produce a valid but incomplete collection. If the feature needs to know which relay supplied or failed to supply each result, use the stream form, where relay provenance and per-item errors remain visible.

Use fetch when the complete, bounded collection is the desired output. Use stream when provenance or early processing must remain visible.

Both finite forms end on an exit policy. The next chapter covers a request intended to stay open: a live subscription.