A tiny document database that lives on your own Cloudflare account. Free tier friendly. Built for lil vibe-coded apps.
const posts = nara.base("posts");
await posts.put({ key: "hello", title: "Hello, world", views: 0 });
await posts.update("hello", { views: inc(1) });
await posts.query(q.gt("views", 10));No ORM, no migrations for your data, no connection strings. JSON in, JSON out.
Beta. Great for side projects and internal tools. Expect breaking changes before 1.0.
- Put/get/query JSON items — schemas optional, add one when you want validation
- Patch updates —
inc(),append(),remove()instead of read-modify-write - Optimistic concurrency —
ifVersionso concurrent writes can't clobber each other - Full-text search — real SQLite FTS5, declared next to your schema
- Your infra — two Workers + one D1 database per app, all on Cloudflare's free plan
- One-command setup —
nara initprovisions and deploys everything
Needs Node 22+.
git clone https://github.com/abdelhai/nara && cd nara
npm install
npm run devIn a second terminal, mint a key:
npx @naradb/cli create-key demo --localThen use it:
import { Nara } from "@naradb/sdk";
const nara = new Nara({ appId: "demo", key: "nara_sk_…" }); // defaults to local dev
const posts = nara.base("posts");
await posts.put({ key: "hello", title: "Hello, world" });
console.log(await posts.get("hello"));That's it. You have a database.
npx wrangler login
npx @naradb/cli init # provision + deploy both Workers
npx @naradb/cli add-app blog --with-key # create an app + mint its API keyCopy the printed nara_sk_… key (shown once), then point the SDK at your deployment:
import { Nara, q, inc } from "@naradb/sdk";
const nara = new Nara({
appId: "blog",
key: process.env.NARA_KEY!,
host: "https://nara-data.<you>.workers.dev",
});
const posts = nara.base<{ title: string; author: string; views: number }>("posts");
await posts.put({ key: "hello-world", title: "Hello", author: "ada", views: 0 });
await posts.update("hello-world", { views: inc(1) });
const { items } = await posts.query(q.and(q.eq("author", "ada"), q.gt("views", 10)));| Doc | For |
|---|---|
| SDK reference | Using the database from code — queries, schemas, search, errors |
| CLI reference | init, add-app, create-key |
| HTTP API | Raw wire format — for direct callers and agents debugging requests |
| Queries & search | The filter DSL and FTS5 search, in depth |
| Deploy & operate | Running your instance — credentials, local dev, troubleshooting |
| Architecture | How it's built and why — for contributors |
- One D1 database per app — designed for many small-to-medium datasets, not one giant one. D1's limits apply per app (10 GB each).
contains/not_containsqueries scan in memory with a cap — checktruncatedon large bases, or use full-text search instead.- One data-plane deployment holds ~5,000 apps (Cloudflare's D1 binding limit per Worker).
- No multi-region magic beyond what D1 itself does.
npm run dev # both Workers: data on :8090, control on :8091
npm test # all workspaces| Path | What |
|---|---|
data/ |
Data plane Worker: items, queries, search |
control/ |
Control plane Worker: apps, keys, token exchange |
sdk/ |
TypeScript client (@naradb/sdk) |
cli/ |
nara CLI (@naradb/cli) |
Local config lives in each Worker's gitignored .dev.vars — completely separate from deployed config, so local dev never touches production secrets.