{"id":29481480,"url":"https://github.com/pippellia-btc/rely","last_synced_at":"2026-03-10T14:04:18.492Z","repository":{"id":286660367,"uuid":"954006425","full_name":"pippellia-btc/rely","owner":"pippellia-btc","description":"A framework for building custom Nostr relays you can rely on. Designed for the best developer experience.","archived":false,"fork":false,"pushed_at":"2026-01-13T17:23:46.000Z","size":565,"stargazers_count":22,"open_issues_count":2,"forks_count":6,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-13T19:22:48.653Z","etag":null,"topics":["framework","golang","nostr","nostr-relay"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pippellia-btc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-03-24T12:25:04.000Z","updated_at":"2026-01-13T17:23:50.000Z","dependencies_parsed_at":null,"dependency_job_id":"8b6acfe9-f5cd-4838-96f0-2648b37ee843","html_url":"https://github.com/pippellia-btc/rely","commit_stats":null,"previous_names":["pippellia-btc/rely"],"tags_count":27,"template":false,"template_full_name":null,"purl":"pkg:github/pippellia-btc/rely","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pippellia-btc%2Frely","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pippellia-btc%2Frely/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pippellia-btc%2Frely/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pippellia-btc%2Frely/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pippellia-btc","download_url":"https://codeload.github.com/pippellia-btc/rely/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pippellia-btc%2Frely/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30336093,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-10T12:41:07.687Z","status":"ssl_error","status_checked_at":"2026-03-10T12:41:06.728Z","response_time":106,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["framework","golang","nostr","nostr-relay"],"created_at":"2025-07-15T00:02:41.267Z","updated_at":"2026-03-10T14:04:18.455Z","avatar_url":"https://github.com/pippellia-btc.png","language":"Go","funding_links":[],"categories":["Go","Relays"],"sub_categories":["Implementations"],"readme":"# rely\nA framework for building super custom [Nostr](https://github.com/nostr-protocol/nostr) relays you can *rely* on. Written in Go, it's designed to be simple and performant, while providing an exeptional developer experience.\n\n\u003ca href=\"https://pkg.go.dev/github.com/pippellia-btc/rely\"\u003e\u003cimg src=\"https://pkg.go.dev/badge/github.com/pippellia-btc/rely.svg\" alt=\"Go Reference\"\u003e\u003c/a\u003e\n[![Go Report Card](https://goreportcard.com/badge/github.com/pippellia-btc/rely)](https://goreportcard.com/report/github.com/pippellia-btc/rely)\n\n## Installation\n```\ngo get github.com/pippellia-btc/rely\n```\n\n## Simple and Customizable\nGetting started is easy, and deep customization is just as straightforward.\n\n```golang\npackage main\n\nimport (\n\t\"context\"\n\t\"os/signal\"\n\t\"syscall\"\n\n\t\"github.com/pippellia-btc/rely\"\n)\n\nfunc main() {\n\tctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)\n\tdefer cancel()\n\n\trelay := rely.NewRelay()\n\n\terr := relay.StartAndServe(ctx, \"localhost:3334\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n```\n\n### Structural Customization\nFine-tune core parameters using functional options:\n\n```golang\nrelay := rely.NewRelay(\n\trely.WithAuthURL(\"myDomain.com\"),\t// required for NIP-42 validation\n\trely.WithLogger(myLogger),\t\t\t// configure the relay logger\n\trely.WithInfo(myRelayInfo)\t\t\t// set up nip-11 information document\n)\n```\n\nTo find all the available options and documentation, see [options.go](/options.go).\n\n### Behavioral Customization\n\nYou are not limited to simple configuration variables. The relay architecture facilitates complete behavioral customization by allowing you to inject your own functions into its `Hooks`. This gives you full control over the connection lifecycle, event flow and rate-limiting, enabling any custom business logic.\n\nBelow is a silly example that illustrates rely's flexibility.\n\n```golang\nfunc main() {\n\t// ...\n\trelay.Reject.Connection.Append(BadIP)\n\trelay.Reject.Event.Append(RejectSatan)\n\trelay.On.Event = Save\t// your custom DB save\n}\n\nfunc BadIP(s Stats, req *http.Request) error {\n\tif slices.Contains(blacklist, rely.GetIP(req).Group()) {\n\t\treturn fmt.Errorf(\"you shall not pass!\")\n\t}\n\treturn nil\n}\n\nfunc RejectSatan(client Client, event *nostr.Event) error {\n\tif event.Kind == 666 {\n\t\tblacklist = append(blacklist, client.IP().Group())\n\t\tclient.Disconnect()\n\t\treturn errors.New(\"not today, Satan. Not today\")\n\t}\n\treturn nil\n}\n```\n\nYou can find all the available hooks and documentation, in [hooks.go](/hooks.go).  \nIf you need additional hooks, don't hesitate to [open an issue](https://github.com/pippellia-btc/rely/issues/new)!\n\nIf you are looking for inspiration or examples, check out the [examples](/examples/) directory.\n\n### Intuitive interfaces\n\nIf you've read the [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md), you already know how to use rely.  \nDon't believe it? These three are the only interfaces that you'll have to deal with:\n\n- [Relay](/relay.go)\n- [Client](/client.go)\n- [Subscription](/subscription.go)\n\nNo obscure structures leaking implementation details, just intuitive methods that almost require no explanation. As an example, these are some of the methods of the `Client` interface (all safe for concurrent use).\n\n```golang\ntype Client interface {\n\tIP() IP\n\t\n\tPubkeys() []string\n\n\tConnectedAt() time.Time\n\n\tSubscriptions() []Subscription\n\n\tSendNotice(msg string)\n\n\tSendAuth()\n\n\tDisconnect()\n\n\t// All other methods...\n\t// (don't fear, there are be comments and docs for all methods)\n}\n```\n\n## Performant\n\nRely is completely written in Go, a memory-safe and low level language that is easy to learn and performant. It's particularly fit for building server-side applications thanks to its powerful concurrency primitives like channels and goroutines.\n\nThese features are extensively used in rely, creating a lock-free architecture on the hot-paths.  \nInspired by [strfry](https://github.com/hoytech/strfry), rely also implements inverted indexes for matching broadcasted events with subscriptions, making this crucial operation very efficient. For more, check out the [architecture section](#architecture).\n\nAll of these optimizations allow a [dummy implementation](https://github.com/pippellia-btc/rely/blob/main/tests/random_test.go) using rely to serve 8000+ concurrent \"spammy\" clients with less than 1GB of RAM on a 2017 i5 CPU (4-cores).\n\n## Secure by Design\n\nTo prevent resource abuse, each client is assigned a fixed-size queue for outgoing messages.  \nBefore processing each `REQ`, the relay calculates the remaining free space and uses that number as a hard cap for the filters' limits. If the total request exceeds the budget, the larger filters are scaled down proportionally.\n\nHere is an example illustrating it.\n``` \n1. client's queue has a free capacity of 300.\n\n2. client sends a REQ with two filters f1 and f2.\n\tf1: limit=10\t\tf2: no limit\n\n3. the filters' limits are modified to match the free capacity:\n\tf1: limit=10\t\tf2: limit=290\n```\n\nThis prevents waste of CPU and bandwidth on events the client will not see, and penalizes clients that request more than they consume. The size of the client's queue can be customized with the appropriate [option](/options.go).\n\n## Architecture\n\n![](architecture.png)\n\n### Client\n\nThe Client is the middleman between the websocket connection and the Relay. It reads and writes to the websocket in two separated goroutines, ensuring proper serialization.\n\n**Client.read**:\n- reads from the websocket and parses nostr messages\n- applies the user defined `Reject` hooks\n- sends to the Processor's queue\n- handles NIP-42 authentication\n\n**Client.write**:\n- receives responses in a dedicated queue\n- writes them to the websocket\n\n### Processor\n\nThe Processor received incoming requests (REQs, EVENTs, COUNTs), and handles them by applying the user defined `On` hooks (e.g. `On.Event`, `On.Req`, `On.Count`). It consumes from a dedicated queue with a limited but configurable number of worker goroutines.\n\n### Dispatcher\n\nThe Dispatcher is responsible for broadcasting newly received events to all matching subscriptions.\nTo accomplish this task efficiently, it maintains inverted indexes for all active subscriptions of all clients, an approach inspired by [strfry](https://github.com/hoytech/strfry).\n\nHowever, the Dispatcher is not the ultimate authority on the subscriptions state. Each client is the authority for its own subscriptions, while the dispatcher only maintains an eventually-consistent snapshot of the active ones. This decision is motivated by two reasons:\n\n1. having CLOSEs cancel the subscriptions as fast as possible to save work.\n2. making calls to `Client.Subscriptions` very efficiently.\n\n## Well tested\n\nRely fetures unit tests for components that make sense to test in isolation.\nMore importantly, we have a [random stress test](https://github.com/pippellia-btc/rely/blob/main/tests/stress_test.go) where the relay is bombarded with thousands of connections, events, filters, and abrupt disconnections every second. This test alone allowed the discovery of hard concurrency bugs and race conditions impossible to detect with simplistic unit tests.\n\n## Used by\n\nThis section lists project and repositories that are using rely in production.\n\n- [ContextVM](https://github.com/ContextVM/contextvm-relay)\n- [Zapstore](https://github.com/zapstore/server)\n- [Vertex](https://github.com/vertex-lab/relay)\n- [NextBlock](https://primal.net/e/nevent1qqsy0nfg6tqzrtkaeggp7jm5y8c89jrcnd3z64a2fvhzqg4jgyefyucye2qfu)\n\n## Databases\n\nRely doesn't come with a default database, you have to provide your own.  \nFortunately, the community has developed several ready-to-use database implementations:\n- [nostr-sqlite](https://github.com/vertex-lab/nostr-sqlite): a performant and highly customizable SQLite store for Nostr events.\n- [eventstore](https://pkg.go.dev/fiatjaf.com/nostr@v0.0.0-20251125175014-6367bd71d42a/eventstore): A collection of reusable database connectors, wrappers and schemas that store Nostr events\n\n## FAQs\n\n\u003cdetails\u003e\n\u003csummary\u003eWhy does On.Req accept multiple filters?\u003c/summary\u003e\n\nAs per [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md), a REQ can contain multiple filters. Returning only one filter (at the time) would prevent the user of rely from implementing custom query optimizations.\n\nYou can always loop through the filters and make a query for each.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eWhen Negentropy?\u003c/summary\u003e\nIt's in the roadmap.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eWhen NIP-86?\u003c/summary\u003e\n\nDoes anyone uses NIP-86? If so, please let me know.  \nJust so you know, you can embedd the `Relay` inside a `http.Server`, where you can configure all the API routes you want,\nas shown in [this example](/examples/custom-api/main.go)\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eHow can I add my custom API?\u003c/summary\u003e\n\nYou can embedd the `Relay` inside a `http.Server`, where you can configure all the API routes you want,\nas shown in [this example](/examples/custom-api/main.go)\n\n\u003c/details\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpippellia-btc%2Frely","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpippellia-btc%2Frely","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpippellia-btc%2Frely/lists"}