Skip to content

SQL Databases ​

Nostrify can store events in Postgres, SQLite, and more thanks to Kysely.

You need the additional @nostrify/db package, which offers a general-purpose SQL storage NDatabase and a custom Postgres storage NPostgres.

Both classes accepts a Kysely instance and then execute queries on it. First create a Kysely instance and connect it to whichever database you choose, then pass it to a Nostrify storage class.

ts
import { NDatabase } from '@nostrify/db';
import { Kysely } from 'kysely';

const kysely = new Kysely(/* set up your database */);

const db = new NDatabase(kysely);
await db.migrate();

Usage ​

NDatabase implements NStore, allowing you to use it interchangeably with relays.

Insert an event ​

ts
await db.event(event);

Query events ​

ts
const events = await db.query([{ kinds: [1, 6], limit: 5 }]);

Count events ​

ts
const { count } = await db.count([{ kinds: [1, 6] }]);

Remove events ​

ts
await db.remove([{ kinds: [1, 6] }]);

NDatabase supports NIP-50 search with the fts option:

ts
const db = new NDatabase(kysely, {
  fts: 'sqlite',
});

Search filters ​

Once enabled, you can query with search filters:

ts
const events = await db.query([{ kinds: [1], search: 'hello world' }]);

NOTE

If FTS is not enabled, the search filter will always return an empty array.

Custom tag indexes ​

By default, NDatabase will index all single-letter tags. For more control, add a custom indexTags function:

ts
const db = new NDatabase(kysely, {
  indexTags(event: NostrEvent): string[][] {
    // Return the tags that you want to index!
    return event.tags.filter(([name]) => ['a', 'd', 'e', 'proxy'].includes(name));
  },
});

Tables ​

NDatabase manages two tables:

  • nostr_events stores Nostr events. Each property has its own column.
  • nostr_tags stores tags to be indexed for tag filters.

TIP

By default, all single-letter tags are indexed. You can customize this behavior by passing a custom indexTags function into NDatabase.

NOTE

If FTS is enabled, one of the following tables will also be created:

  • nostr_pgfts to store the Postgres search index.
  • nostr_fts5 to store the SQLite search index.

Migrating the database ​

Run await db.migrate() to create the necessary tables and indexes before use. You should call this every time the program starts.

SQLite on Deno ​

Using @db/sqlite and @soapbox/kysely-deno-sqlite, you can connect to an SQLite database in Deno.

ts
import { Database } from '@db/sqlite';
import { DenoSqlite3Dialect } from '@soapbox/kysely-deno-sqlite';
import { Kysely } from 'kysely';

const kysely = new Kysely({
  dialect: new DenoSqlite3Dialect({
    database: new Database('./nostr.sqlite3'),
  }),
});

const db = new NDatabase(kysely);
await db.migrate();

Postgres on Deno ​

Using x/postgresjs you can connect to a Postgres database in Deno.

ts
import { NDatabase } from '@nostrify/nostrify';
import { PostgresJSDialect } from 'kysely-postgres-js';
import { Kysely } from 'kysely';
import postgres from 'postgres';

const kysely = new Kysely<Database>({
  dialect: new PostgresJSDialect({
    postgres: postgres({
      database: 'test',
      hostname: 'localhost',
      user: 'postgres',
      password: 'postgres',
      port: 5432,
    }),
  }),
})

const db = new NDatabase(kysely);
await db.migrate();

TIP

There are a few different Postgres drivers for Deno. See which one works best for you.

Other databases ​

Kysely maintains a list of supported dialects.

It may be possible to get other dialects working. Or build your own!

Soapbox