A utility library that allows JS/TS apps to effortlessly fetch past events from Nostr relays.
npm install nostr-fetch
deno add npm:nostr-fetch
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>
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 });
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 }
)
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"}`);
}
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!
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 |
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) {
// ...
}
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>
- class
NostrFetcher
- Initializers and Finilizers
- Fetch Methods
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.
Initializes a NostrFetcher
instance based on the default relay pool implementation.
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.
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!
}
All methods are instance methods of NostrFetcher
.
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.
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.
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.
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.
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.
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.
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.
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.
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