D1 Relay — Documentation

A personal Nostr relay running on Cloudflare Workers + D1 (SQLite). Free tier, no wrangler, no build step.

Features

  • NIP-20 OK codes — every EVENT gets an explicit result (stored, duplicate, invalid, error).
  • NIP-45 COUNT — ask how many events match a filter.
  • Tag filters — query by #e, #p, #t, etc., via an indexed event_tags table.
  • Prefix matching — ids/authors filters accept partial prefixes (8+ hex chars).
  • Hardened validation — real signature verification plus an independent id re-check.
  • NIP-11 — advertises supported NIPs, limitations and retention.

Files

v5/
├── schema.sql            D1 schema (commented; use with wrangler/tools)
├── schema-console.sql    Same schema, NO comments — paste this into the
│                         D1 dashboard Console (it rejects comments)
├── worker/
│   ├── nostr-tools.js    crypto library (self-contained)
│   ├── worker.js         the relay (imports nostr-tools.js)
│   └── worker.single.js  optional single-file bundle of the two
├── tools/
│   ├── build-single.mjs  regenerates worker.single.js
│   └── build-site.mjs    regenerates the site assets
├── tests/
│   ├── local.mjs         offline test harness (56 checks, no installs)
│   └── run.js            live test of a deployed relay
└── tester/               standalone browser tester (works offline)

worker.single.js is optional — it exists as a paste-in fallback. You only ever need one of: two files (worker.js + nostr-tools.js) or one file (worker.single.js).

Deploy (Cloudflare dashboard, no wrangler)

1. Create the database

  1. Cloudflare dashboard → Workers & Pages → D1 → Create database.
  2. Name it (e.g. relay).
  3. Open the database → Console → paste the contents of v5/schema-console.sql → run.

⚠️ Use schema-console.sql, not schema.sql. The D1 Console fails with "Requests without any query are not supported" when the SQL contains -- comment lines (a known D1 parser bug). The console version is the same schema with no comments.

2. Create the Worker

  1. Workers & Pages → Create → Worker → Edit code.
  2. Multi-file: delete the default worker.js, then add worker.js and nostr-tools.js from v5/worker/ as separate files.
  3. Or single-file: paste worker.single.js into worker.js.
  4. Save and Deploy.

3. Bind the database

  1. Worker → Settings → Variables → D1 database bindings → Add binding.
  2. Variable name must be exactly DB.
  3. Select the database you created.

4. Test it

  • Visit https://d1relay.nostrweb.workers.dev — you should see relay info (NIP-11) JSON.
  • Or open the tester and connect.
  • Or run the live checker: node v5/tests/run.js wss://d1relay.nostrweb.workers.dev

Run the tests locally

No npm install, no internet — just Node 22.5+ (built-in node:sqlite).

node v5/tests/local.mjs

Runs the relay (both modular and single-file) against an in-memory database and checks 56 things: storing, duplicates, bad signatures, malformed messages, prefix lookups, tag filters, COUNT, and limits.

To test a deployed relay over the network:

node v5/tests/run.js https://your-worker.workers.dev

Schema

events — one row per stored event:

  • id — 64-hex event id (primary key, so duplicates are rejected)
  • pubkey, created_at, kind, content
  • raw_event — the full original JSON, returned verbatim

event_tags — a row per tag, for fast #tag filters: event_id, name, value, position.

Relay behavior

MessageResponse
["EVENT", {...}] valid["OK", id, true, "stored"]
duplicate["OK", id, false, "duplicate: event already stored"]
invalid (shape/sig/id/too big/future)["OK", id, false, "invalid: ..."]
db write fails["OK", id, false, "error: db write failed"]
["REQ", sub, filter]["EVENT", ...] matches, then ["EOSE", sub]
["COUNT", sub, filter]["COUNT", sub, {count: N}]
malformed / too big["NOTICE", "..."]
too many events/reqs in 10s["NOTICE", "rate limited"]

Limits

  • Max incoming message: 128 KB
  • Max event content: 64 KB
  • Max tags per event: 2000
  • Max results per REQ: 500 (default 50)
  • Events no more than 1 hour in the future
  • At most 30 EVENTs and 60 REQs per 10 seconds

Version log

VersionWhat changed
v1Minimal relay (EVENT, REQ, EOSE).
v2NIP-11 info, shape validation, id check, multi-filter REQ, limit cap.
v2.5Inlined crypto library, real signature verification, rate limiting.
v3Stored full raw event JSON; ids filter.
v5NIP-20 OK codes, tag filters, NIP-45 COUNT, prefix matching, size caps, new event_tags schema, offline tests, updated tester.