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

Run an embedded relay

nostr-sdk can also host a relay inside an application process. This is useful for deterministic integration tests, device-local services, and applications that need a relay with their own storage and policy. Rust enables it with the local-relay feature; native packages include it where the platform can listen on a socket.

Start a persistent relay

The example uses LMDB so published events remain available after a restart. For a test fixture, replace it with an isolated in-memory database owned by the test.

Rust

Enable the relay API and add the database used by the example:

[dependencies]
nostr-lmdb = "0.45"
nostr-sdk = { version = "0.45", features = ["local-relay"] }
use std::time::Duration;

use nostr_lmdb::NostrLmdb;
use nostr_sdk::prelude::*;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let database = NostrLmdb::open("./data/relay").await?;
    let relay = LocalRelay::builder().database(database).port(7777).build();

    // Start the relay.
    relay.run().await?;

    println!("Relay listening on {}", relay.url().await);

    // Keep the process running
    loop {
        tokio::time::sleep(Duration::from_secs(60)).await;
    }
}
Python
import asyncio

from nostr_sdk import LocalRelayBuilder, NostrLmdb


async def main() -> None:
    database = await NostrLmdb.open("./data/relay")
    relay = LocalRelayBuilder().database(database).port(7777).build()

    await relay.run()
    print(f"Relay URL: {await relay.url()}")

if __name__ == "__main__":
    asyncio.run(main())
JavaScript
Node.js
import { LocalRelayBuilder, NostrLmdb } from "@nostrdevkit/nostr-sdk-node";

const database = await NostrLmdb.open("./data/relay");
const relay = new LocalRelayBuilder()
    .database(database)
    .port(7777)
    .build();

await relay.run();
console.log("Relay URL:", (await relay.url()).toString());
Web

Browsers cannot listen on a TCP port and cannot host an embedded relay. Connect the Web client to a relay running in another process or service.

React Native
import {
    LocalRelayBuilder,
    NostrLmdb,
} from "@nostrdevkit/nostr-sdk-react-native";

export async function runEmbeddedRelay(databasePath: string) {
    const database = await NostrLmdb.open(databasePath);
    const relay = new LocalRelayBuilder()
        .database(database)
        .port(7777)
        .build();

    await relay.run();
    console.log("Relay URL:", (await relay.url()).toString());

}

Pass an absolute path inside the application’s data directory as databasePath.

Kotlin
package rust.nostr.snippets

import kotlinx.coroutines.runBlocking
import org.nostrdevkit.sdk.LocalRelayBuilder
import org.nostrdevkit.sdk.NostrLmdb

suspend fun runEmbeddedRelay() {
    val database = NostrLmdb.open("./data/relay")
    val relay = LocalRelayBuilder()
        .database(database)
        .port(7777u)
        .build()

    relay.run()
    println("Relay URL: ${relay.url()}")

}

fun relayMain() = runBlocking {
    runEmbeddedRelay()
}

On Android, replace the example path with one inside the application files directory.

Swift
import Foundation
import NostrSDK

func runEmbeddedRelay(path: String) async throws {
    let database = try await NostrLmdb.open(path: path)
    let relay = LocalRelayBuilder()
        .database(database: database)
        .port(port: 7777)
        .build()

    try await relay.run()
    print("Relay URL: \(await relay.url())")
}

Pass a URL-derived path inside Application Support or another application-owned directory.

C#
namespace Snippets;

using Nostr.Sdk;

public static class RelayExample
{
    public static async Task RunEmbeddedRelay(string path)
    {
        var database = await NostrLmdb.Open(path);
        var relay = new LocalRelayBuilder()
            .Database(database)
            .Port(7777)
            .Build();

        await relay.Run();
        Console.WriteLine($"Relay URL: {await relay.Url()}");
    }
}

Keep the handle in the component that owns the service. Dropping its last owner stops the relay.

LocalRelayBuilder also exposes listener, limit, NIP-42, and read/write policy options. Continue with Where to go next when the relay needs configuration beyond this minimal setup.