Skip to content
abdelhaiPublic

About

☄️ A nifty (and probably free) document store built to run on Cloudflare Workers & D1.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

Nara

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.

What you get

  • 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 — ifVersion so 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 init provisions and deploys everything

Try it locally (2 minutes, no Cloudflare account)

Needs Node 22+.

git clone https://github.com/abdelhai/nara && cd nara
npm install
npm run dev

In a second terminal, mint a key:

npx @naradb/cli create-key demo --local

Then 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.

Deploy for real

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 key

Copy 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)));

Docs

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

Good to know

  • 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_contains queries scan in memory with a cap — check truncated on 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.

Hacking on Nara

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.

About

☄️ A nifty (and probably free) document store built to run on Cloudflare Workers & D1.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages