Awesome
nostr-fetch
A utility library that allows JS/TS apps to effortlessly fetch past events from Nostr relays.
Installation
for npm Project
npm install nostr-fetch
for Deno Project
deno add npm:nostr-fetch
for Browser Apps, without Bundlers
You can also use nostr-fetch in your HTML via <script>
tags, thanks to jsDelivr.
<script type="module">
import { NostrFetcher } from "https://cdn.jsdelivr.net/npm/nostr-fetch@0.16.0/+esm"
// ...
</script>
Note for Users on Node.js < v22
Node.js < v22 doesn't have native WebSocket implementation.
On Such a environment you may want to pass a custom WebSocket
constructor from an external package like ws
to NostrFetcher.init()
as an option.
npm install ws
import { NostrFetcher } from "nostr-fetch";
import WebSocket from "ws";
const fetcher = NostrFetcher.init({ webSocketConstructor: WebSocket });
Usage
Basics
import { NostrFetcher } from "nostr-fetch";
const nHoursAgo = (hrs: number): number =>
Math.floor((Date.now() - hrs * 60 * 60 * 1000) / 1000);
const fetcher = NostrFetcher.init();
const relayUrls = [/* relay URLs */];
// fetches all text events since 24 hr ago in streaming manner
const postIter = fetcher.allEventsIterator(
relayUrls,
/* filter (kinds, authors, ids, tags) */
{ kinds: [ 1 ] },
/* time range filter (since, until) */
{ since: nHoursAgo(24) },
/* fetch options (optional) */
{ skipFilterMatching: true }
);
for await (const ev of postIter) {
console.log(ev.content);
}
// fetches all text events since 24 hr ago, as a single array
const allPosts = await fetcher.fetchAllEvents(
relayUrls,
/* filter */
{ kinds: [ 1 ] },
/* time range filter */
{ since: nHoursAgo(24) },
/* fetch options (optional) */
{ sort: true }
)
Various Fetch Methods
import { NostrFetcher } from "nostr-fetch";
const fetcher = NostrFetcher.init();
const relayUrls = [/* relay URLs */];
// fetches latest 100 text posts
// internally:
// 1. fetch latest 100 events from each relay
// 2. merge lists of events
// 3. take latest 100 events
const latestPosts: NostrEvent[] = await fetcher.fetchLatestEvents(
relayUrls,
/* filter */
{ kinds: [ 1 ] },
/* number of events to fetch */
100,
);
// fetches the last metadata event published by pubkey "deadbeef..."
// internally:
// 1. fetch the last event from each relay
// 2. take the latest one
const lastMetadata: NostrEvent | undefined = await fetcher.fetchLastEvent(
relayUrls,
/* filter */
{ kinds: [ 0 ], authors: [ "deadbeef..." ] },
);
// fetches latest 10 text posts from each author in `authors`
const postsPerAuthor = fetcher.fetchLatestEventsPerAuthor(
/* authors and relay set */
// you can also pass a `Map` which has mappings from authors (pubkey) to reley sets,
// to specify a relay set for each author
{
authors: ["deadbeef...", "abcdef01...", ...],
relayUrls,
},
/* filter */
{ kinds: [ 1 ] },
/* number of events to fetch for each author */
10,
);
for await (const { author, events } of postsPerAuthor) {
console.log(`posts from ${author}:`);
for (const ev of events) {
console.log(ev.content);
}
}
// fetches the last metadata event from each author in `authors`
const metadataPerAuthor = fetcher.fetchLastEventPerAuthor(
/* authors and relay set */
// you can also pass a `Map` which has mappings from authors (pubkey) to reley sets,
// to specify a relay set for each author
{
authors: ["deadbeef...", "abcdef01...", ...],
relayUrls,
}
/* filter */
{ kinds: [ 0 ] },
);
for await (const { author, event } of metadataPerAuthor ) {
console.log(`${author}: ${event?.content ?? "not found"}`);
}
Working with custom relay pool implementations
First, install the adapter package for the relay pool implementation you want to use.
For example, if you want to use nostr-fetch with nostr-tools' SimplePool
:
npm install @nostr-fetch/adapter-nostr-tools
Then, wrap your relay pool instance with the adapter and pass it to the initializer NostrFetcher.withCustomPool()
.
import { NostrFetcher } from "nostr-fetch";
import { simplePoolAdapter } from "@nostr-fetch/adapter-nostr-tools";
import { SimplePool } from "nostr-tools";
const pool = new SimplePool();
// wrap SimplePool with simplePoolAdapter to make it interoperable with nostr-fetch
const fetcher = NostrFetcher.withCustomPool(simplePoolAdapter(pool));
// now, you can use any fetch methods described above!
Table of Available Adapters
Package | Relay Pool Impl. | Adapter Package | Adapter |
---|---|---|---|
nostr-tools (v1) | SimplePool | @nostr-fetch/adapter-nostr-tools | simplePoolAdapter |
nostr-tools (v2) | SimplePool | @nostr-fetch/adapter-nostr-tools-v2 | simplePoolAdapter |
nostr-relaypool | RelayPool | @nostr-fetch/adapter-nostr-relaypool | relayPoolAdapter |
@nostr-dev-kit/ndk | NDK | @nostr-fetch/adapter-ndk | ndkAdapter |
rx-nostr (v1) | RxNostr | @nostr-fetch/adapter-rx-nostr | rxNostrAdapter |
Cancelling by AbortSignal
import { NostrFecher } from "nostr-fetch"
const fetcher = NostrFetcher.init();
const relayUrls = [/* relay URLs */];
const evIter = fetcher.allEventsIterator(
relayUrls,
{/* filter */},
{/* time range */},
/* pass an `AbortSignal` here to enable cancellation! */
{ signal: AbortSignal.timeout(1000) },
);
for await (const ev of evIter) {
// ...
}
Examples
You can find example codes under packages/examples
directory.
To run examples, follow the steps (using npm
for example):
# first time only: install dependencies & build subpackages
npm install && npm run build
# then, execute example
# the command executes packages/examples/src/fetchAll.ts
npm run example fetchAll
# some examples takes a hex pubkey as an argument
npm run example fetchLastPerAuthor <your hex pubkey>
API
- class
NostrFetcher
- Initializers and Finilizers
- Fetch Methods
class NostrFetcher
The entry point of Nostr events fetching.
It manages connections to Nostr relays under the hood. It is recommended to reuse single NostrFetcher
instance in entire app.
You should instantiate it with following initializers instead of the constructor.
Initializers and Finalizers
NostrFetcher.init
Initializes a NostrFetcher
instance based on the default relay pool implementation.
NostrFetcher.withCustomPool
Initializes a NostrFetcher
instance based on a custom relay pool implementation passed as an argument.
This opens up interoperability with other relay pool implementations such as nostr-tools' SimplePool
. See here for details.
NostrFetcher#shutdown
Cleans up the internal relay pool.
If you use a fetcher instance initialized via NostrFetcher.init
, calling this method closes connections to all the connected relays.
You can use a variable with using
keyword to automatically shutdown a fetcher instance when the scope of the using
variable ends. Roughly speaking, withFinally()
and withUsing()
in the code bellow are the same.
async function withFinally() {
const fetcher = NostrFetcher.init();
try {
// do some work with the fetcher...
} finally {
fetcher.shutdown();
}
}
async function withUsing() {
using fetcher = NostrFetcher.init();
// do some work with the fetcher...
// the fetcher will be automatically shutdown here!
}
Fetch Methods
All methods are instance methods of NostrFetcher
.
allEventsIterator
public allEventsIterator(
relayUrls: string[],
filter: FetchFilter,
timeRangeFilter: FetchTimeRangeFilter,
options?: AllEventsIterOptions
): AsyncIterable<NostrEvent>
Returns an async iterable of all events matching the filter from Nostr relays specified by the array of URLs.
You can iterate over events using for-await-of loop.
const fetcher = NostrFetcher.init();
const events = fetcher.allEventsIterator([/* relays */], {/* filter */}, {/* time range */});
for await (const ev of events) {
// process events
}
Specifying enableBackpressure: true
in options
enables "backpressure mode", where the fetcher is backpressured by the consumer of the iterator.
Note
There are no guarantees about the order of returned events. Especially, events are not necessarily ordered in "newest to oldest" order.
fetchAllEvents
public async fetchAllEvents(
relayUrls: string[],
filter: FetchFilter,
timeRangeFilter: FetchTimeRangeFilter,
options?: FetchAllOptions
): Promise<NostrEvent[]>
Fetches all events matching the filter from Nostr relays specified by the array of URLs, and collect them into an array.
If sort: true
is specified in options
, events in the resulting array will be sorted in "newest to oldest" order.
Note
There are no guarantees about the order of returned events if
sort
options is not specified.
fetchLatestEvents
public async fetchLatestEvents(
relayUrls: string[],
filter: FetchFilter,
limit: number,
options?: FetchLatestOptions
): Promise<NostrEvent[]>
Fetches latest up to limit
events matching the filter from Nostr relays specified by the array of URLs.
Events in the result will be sorted in "newest to oldest" order.
fetchLastEvent
public async fetchLastEvent(
relayUrls: string[],
filter: FetchFilter,
options?: FetchLatestOptions
): Promise<NostrEvent | undefined>
Fetches the last event matching the filter from Nostr relays specified by the array of URLs.
Returns undefined
if no event matching the filter exists in any relay.
fetchLatestEventsPerKey
public fetchLatestEventsPerKey<KN extends FetchFilterKeyName>(
keyName: KN,
keysAndRelays: KeysAndRelays<KN>,
otherFilter: FetchFilter,
limit: number,
options?: FetchLatestOptions
): AsyncIterable<NostrEventListWithKey<KN>>
Fetches latest up to limit
events for each key specified by keyName
and keysAndRelays
.
keysAndRelays
can be either of two types:
{ keys: K[], relayUrls: string[] }
: The fetcher will use the same relay set (relayUrls
) for allkeys
to fetch events.Map<K, string[]>
: Key must be the key of event and value must be relay set for that key. The fetcher will use separate relay set for each key to fetch events.
Note
The type
K
isnumber
ifkeyName
is"kinds"
. Otherwise,K
isstring
.
Result is an async iterable of { key: <key of events>, events: <events which have that key> }
pairs.
Each array of events in the result are sorted in "newest to oldest" order.
fetchLastEventPerKey
public fetchLatestEventsPerKey<KN extends FetchFilterKeyName>(
keyName: KN,
keysAndRelays: KeysAndRelays<KN>,
otherFilter: FetchFilter,
options?: FetchLatestOptions
): AsyncIterable<NostrEventWithKey<KN>>
Fetches the last event for each key specified by keysAndRelays
.
keysAndRelays
can be either of two types:
{ keys: K[], relayUrls: string[] }
: The fetcher will use the same relay set (relayUrls
) for allkeys
to fetch events.Map<K, string[]>
: Key must be key of the event and value must be relay set for that key. The fetcher will use separate relay set for each key to fetch events.
Note
The type
K
isnumber
ifkeyName
is"kinds"
. Otherwise,K
isstring
.
Result is an async iterable of { key: <key of events>, event: <the latest event which have that key> }
pairs.
event
in result will be undefined
if no event matching the filter exists in any relay.
fetchLatestEventsPerAuthor
public fetchLatestEventsPerAuthor(
authorsAndRelays: AuthorsAndRelays,
otherFilter: Omit<FetchFilter, "authors">,
limit: number,
options: FetchLatestOptions = {}
): AsyncIterable<{ author: string; events: NostrEvent[] }>
Fetches latest up to limit
events for each author specified by authorsAndRelays
.
It is just a wrapper of fetchLatestEventsPerKey
specialized to "authors"
key.
fetchLastEventPerAuthor
public fetchLastEventPerAuthor(
authorsAndRelays: AuthorsAndRelays,
otherFilter: Omit<FetchFilter, "authors">,
options: FetchLatestOptions = {}
): AsyncIterable<{ author: string; event: NostrEvent | undefined }>
Fetches the last event for each author specified by authorsAndRelays
.
It is just a wrapper of fetchLastEventPerKey
specialized to "authors"
key.
Support me!
You can support this project by:
- ⭐ Starring the repo
- ⚡️ Sending some sats to my lightning address: jiftechnify@eclair.c-stellar.net
- 🐝 Sending funds via PkgZap