diff --git a/README.md b/README.md
index a6f9055..4ed03d0 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,116 @@
-khatru
+# khatru, a relay framework [](https://pkg.go.dev/github.com/fiatjaf/khatru#Relay)
+
+Khatru makes it easy to write very very custom relays:
+
+ - custom event or filter acceptance policies
+ - custom `AUTH` handlers
+ - custom storage and pluggable databases
+ - custom webpages and other HTTP handlers
+
+Here's a sample:
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "net/http"
+
+ "github.com/fiatjaf/khatru"
+ "github.com/nbd-wtf/go-nostr"
+)
+
+func main() {
+ // create the relay instance
+ relay := khatru.NewRelay()
+
+ // set up some basic properties (will be returned on the NIP-11 endpoint)
+ relay.Name = "my relay"
+ relay.PubKey = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
+ relay.Description = "this is my custom relay"
+ relay.IconURL = "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fliquipedia.net%2Fcommons%2Fimages%2F3%2F35%2FSCProbe.jpg&f=1&nofb=1&ipt=0cbbfef25bce41da63d910e86c3c343e6c3b9d63194ca9755351bb7c2efa3359&ipo=images"
+
+ // you must bring your own storage scheme -- if you want to have any
+ store := make(map[string]*nostr.Event, 120)
+
+ // set up the basic relay functions
+ relay.StoreEvent = append(relay.StoreEvent,
+ func(ctx context.Context, event *nostr.Event) error {
+ store[event.ID] = event
+ return nil
+ },
+ )
+ relay.QueryEvents = append(relay.QueryEvents,
+ func(ctx context.Context, filter nostr.Filter) (chan *nostr.Event, error) {
+ ch := make(chan *nostr.Event)
+ go func() {
+ for _, evt := range store {
+ if filter.Matches(evt) {
+ ch <- evt
+ }
+ }
+ close(ch)
+ }()
+ return ch, nil
+ },
+ )
+ relay.DeleteEvent = append(relay.DeleteEvent,
+ func(ctx context.Context, event *nostr.Event) error {
+ delete(store, event.ID)
+ return nil
+ },
+ )
+
+ // there are many other configurable things you can set
+ relay.RejectEvent = append(relay.RejectEvent,
+ func(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
+ if event.PubKey == "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52" {
+ return true, "we don't allow this person to write here"
+ }
+ return false, "" // anyone else can
+ },
+ )
+ relay.OnConnect = append(relay.OnConnect,
+ func(ctx context.Context) {
+ // request NIP-42 AUTH from everybody
+ relay.RequestAuth(ctx)
+ },
+ )
+ relay.OnAuth = append(relay.OnAuth,
+ func(ctx context.Context, pubkey string) {
+ // and when they auth we just log that for nothing
+ log.Println(pubkey + " is authed!")
+ },
+ )
+ // check the docs for more goodies!
+
+ mux := relay.Router()
+ // set up other http handlers
+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("content-type", "text/html")
+ fmt.Fprintf(w, `welcome to my relay!`)
+ })
+
+ // start the server
+ fmt.Println("running on :3334")
+ http.ListenAndServe(":3334", relay)
+}
+```
+
+### But I don't want to write my own database!
+
+Fear no more. Using the https://github.com/fiatjaf/eventstore module you get a bunch of compatible databases out of the box and you can just plug them into your relay. For example, [sqlite](https://pkg.go.dev/github.com/fiatjaf/eventstore/sqlite3):
+
+```go
+ db := sqlite3.SQLite3Backend{DatabaseURL: "/tmp/khatru-sqlite-tmp"}
+ if err := db.Init(); err != nil {
+ panic(err)
+ }
+
+ relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent)
+ relay.QueryEvents = append(relay.QueryEvents, db.QueryEvents)
+ relay.CountEvents = append(relay.CountEvents, db.CountEvents)
+ relay.DeleteEvent = append(relay.DeleteEvent, db.DeleteEvent)
+```
diff --git a/examples/readme-demo/demo-memory b/examples/readme-demo/demo-memory
new file mode 100755
index 0000000..55ffe0b
Binary files /dev/null and b/examples/readme-demo/demo-memory differ
diff --git a/examples/readme-demo/main.go b/examples/readme-demo/main.go
new file mode 100644
index 0000000..d99b510
--- /dev/null
+++ b/examples/readme-demo/main.go
@@ -0,0 +1,87 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "net/http"
+
+ "github.com/fiatjaf/khatru"
+ "github.com/nbd-wtf/go-nostr"
+)
+
+func main() {
+ // create the relay instance
+ relay := khatru.NewRelay()
+
+ // set up some basic properties (will be returned on the NIP-11 endpoint)
+ relay.Name = "my relay"
+ relay.PubKey = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
+ relay.Description = "this is my custom relay"
+ relay.IconURL = "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fliquipedia.net%2Fcommons%2Fimages%2F3%2F35%2FSCProbe.jpg&f=1&nofb=1&ipt=0cbbfef25bce41da63d910e86c3c343e6c3b9d63194ca9755351bb7c2efa3359&ipo=images"
+
+ // you must bring your own storage scheme -- if you want to have any
+ store := make(map[string]*nostr.Event, 120)
+
+ // set up the basic relay functions
+ relay.StoreEvent = append(relay.StoreEvent,
+ func(ctx context.Context, event *nostr.Event) error {
+ store[event.ID] = event
+ return nil
+ },
+ )
+ relay.QueryEvents = append(relay.QueryEvents,
+ func(ctx context.Context, filter nostr.Filter) (chan *nostr.Event, error) {
+ ch := make(chan *nostr.Event)
+ go func() {
+ for _, evt := range store {
+ if filter.Matches(evt) {
+ ch <- evt
+ }
+ }
+ close(ch)
+ }()
+ return ch, nil
+ },
+ )
+ relay.DeleteEvent = append(relay.DeleteEvent,
+ func(ctx context.Context, event *nostr.Event) error {
+ delete(store, event.ID)
+ return nil
+ },
+ )
+
+ // there are many other configurable things you can set
+ relay.RejectEvent = append(relay.RejectEvent,
+ func(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
+ if event.PubKey == "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52" {
+ return true, "we don't allow this person to write here"
+ }
+ return false, "" // anyone else can
+ },
+ )
+ relay.OnConnect = append(relay.OnConnect,
+ func(ctx context.Context) {
+ // request NIP-42 AUTH from everybody
+ relay.RequestAuth(ctx)
+ },
+ )
+ relay.OnAuth = append(relay.OnAuth,
+ func(ctx context.Context, pubkey string) {
+ // and when they auth we just log that for nothing
+ log.Println(pubkey + " is authed!")
+ },
+ )
+ // check the docs for more goodies!
+
+ mux := relay.Router()
+ // set up other http handlers
+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("content-type", "text/html")
+ fmt.Fprintf(w, `welcome to my relay!`)
+ })
+
+ // start the server
+ fmt.Println("running on :3334")
+ http.ListenAndServe(":3334", relay)
+}