Skip to content

Nostr Schema ​

When we gather Nostr events, lookup profiles, and get messages from relays, it's essential that the data is in the right shape. But it's a decentralized network — how do we enforce that?

Enter zod, a schema parsing library. zod lets us define schemas that force the data into the right shape, or outright reject it if it's too far off.

This ensures that runtime data actually matches our types (unlike haphazard type assertions like as NostrEvent). Without this, our application would be prone to errors and potentially even security risks.

Nostrify offers a robust schema module for Nostr, parsing everything from events, to IDs, to relay messages with zod.

Usage ​

We import with the alias n for convenience, similar to zod's z.

ts
import { NSchema as n } from '@nostrify/nostrify';

Parse vs Safe Parse ​

All schemas have a .parse method that throws an error if the data is invalid. If you prefer to handle errors yourself, you can use .safeParse instead. See zod basic usage.

Parse a Nostr Event ​

Ensure the event is in the right shape (but does not verify it).

ts
const event = n.event().parse(data);

Parse and Verify a Nostr Event ​

Parse and verify the event in one go (thanks to nostr-tools and zod .refine).

ts
import { verifyEvent } from 'nostr-tools';

const event = n.event().refine(verifyEvent).parse(data);

Parse a Nostr Filter ​

Parse a NIP-01 filter.

ts
const filter = n.filter().parse(data);

Parse an array of filters.

ts
const filters = n.filter().array().parse(data);

Kind 0 Metadata ​

Profile metadata is usually stringified JSON. You can parse it in a single step.

ts
const metadata = n.json().pipe(n.metadata()).parse(event.content);

If you already have it as JSON, you can of course parse that too.

ts
const metadata = n.metadata().parse(json);

TIP

You can use n.json().pipe() any time you need to parse JSON before another schema, thanks to zod .pipe.

Relay Messages ​

Parse messages from a relay.

Returns a union type like ["EVENT", ...] | ["EOSE", ...] | ["OK", ...] | etc

ts
const msg = n.relayMsg().parse(data);

Specific Message Types ​

You can also parse specific message types.

ts
const eventMsg = n.relayEVENT().parse(data);
const okMsg = n.relayOK().parse(data);
const eoseMsg = n.relayEOSE().parse(data);
const noticeMsg = n.relayNOTICE().parse(data);
const closedMsg = n.relayCLOSED().parse(data);
const authMsg = n.relayAUTH().parse(data);
const countMsg = n.relayCOUNT().parse(data);

Client Messages ​

Parse messages from a client.

Returns a union type like ["EVENT", ...] | ["REQ", ...] | ["COUNT", ...] | etc

ts
const msg = n.clientMsg().parse(data);

Specific Message Types ​

You can also parse specific message types.

ts
const eventMsg = n.clientEVENT().parse(data);
const reqMsg = n.clientREQ().parse(data);
const countMsg = n.clientCOUNT().parse(data);
const closeMsg = n.clientCLOSE().parse(data);
const authMsg = n.clientAUTH().parse(data);

Hex IDs ​

Event IDs and pubkeys can be parsed with the n.id() schema.

ts
const id = n.id().parse('58069df16f472997ef6dcb98fd7df1f8b7efefce183c87dccd302f5ee52fe897');
const pubkey = n.id().parse('38f26c42aaa77430d9ea75accd1eb9b89f305940ec36ce8ad71c1662a952a0ea');

Bech32 IDs ​

Bech32 IDs such as npub, nsec, nprofile, etc. can be parsed with the n.bech32() schema. You can specify the prefix as an argument. Note that this is a regex match and does not attempt to actually parse the data.

ts
const npub = n.bech32('npub').parse('npub1gccadx8a5gq623sk55kyua9kj7ppmfw3hhm6us7ltjxhhzrkpwpsu5p70s');
Soapbox