cd /news/developer-tools/tablinum-local-first-database-for-th… · home topics developer-tools article
[ARTICLE · art-110680] src=tablinum.dev ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Tablinum – Local-first database for the browser

Tablinum, a local-first database for the browser, has been released as an npm package, offering encrypted sync via NIP-59 and built-in collaboration with zero backend. The database stores data in IndexedDB, syncs to Nostr relays with end-to-end encryption, and provides TypeScript inference and reactive bindings for Svelte.

read1 min views1 publishedAug 25, 2026

Encrypted sync. Built-in collaboration. Zero backend.

$ npm install tablinum

Every record is gift-wrapped with NIP-59 encryption. The relay stores and forwards encrypted blobs — it never sees your data.

A complete toolkit for local-first applications

Your app works offline. All data lives on your device in IndexedDB. No server required, no spinners.

Data syncs to Nostr relays, encrypted end-to-end with NIP-59 gift wrapping. Relay operators cannot read your data.

Share databases with invite links. Key rotation on member removal — like changing locks when a roommate moves out.

Define schemas with collection() and field.*() builders. Full TypeScript inference from schema to query results.

First-class reactive bindings using async runes. Queries inside $derived(await ...) auto-update when data changes.

Effect-based structured logging with timing spans. From full debug output to complete silence in one config line.

Use the Effect API for full control, or the Svelte API for reactive simplicity

import { Tablinum, collection, field } from "tablinum/svelte";

const schema = {
  todos: collection("todos", {
    title: field.string(),
    done: field.boolean(),
  }, { indices: ["done"] }),
};

export const db = new Tablinum({
  schema,
  relays: ["wss://relay.example.com"],
});

export const todos = db.collection("todos");
js
<script lang="ts">
  import { todos } from "$lib/db";

  let title = $state("");
  let pending = $derived(await todos.where("done").equals(false).get());

  async function addTodo() {
    await todos.add({ title, done: false });
    title = "";
  }
</script>

<form onsubmit={addTodo}>
  <input bind:value={title} placeholder="Add a todo..." />
  <button type="submit">Add</button>
</form>

{#each pending as todo (todo.id)}
  <div>{todo.title}</div>
{/each}

Data lives in IndexedDB on your device. Reads are instant, writes never block.

Every record is wrapped in NIP-59 gift wrapping. Relays see encrypted blobs, nothing more.

NIP-77 negentropy figures out what's missing and only transfers the difference.

Available everywhere

Your data arrives on every device and every collaborator, fully decrypted and ready to use.

── more in #developer-tools 4 stories · sorted by recency
── more on @tablinum 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/tablinum-local-first…] indexed:0 read:1min 2026-08-25 ·