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.
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;
}
}
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())
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());
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.
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.
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.
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.
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.