{"slug": "show-hn-baerly-storage-a-document-db-that-runs-per-request-no-db-server", "title": "Show HN: Baerly-storage, a document DB that runs per request, no DB server", "summary": "Baerly-storage, a document database that runs per request without a dedicated server, was released on Hacker News. The database lives in an S3/R2 bucket and executes entirely within an HTTP request handler, eliminating the need for a database server, daemon, or runtime. It offers a small API with 8 verbs, supports reactive queries, and includes an export tool to PostgreSQL.", "body_md": "**A database with no server. No daemon. No database runtime. Just your app and a bucket.**\n\nA document database that lives *in* an S3/R2 bucket you already own. No new vendor to clear, nothing resident to keep running, and an API small enough for an open-weights LLM to zero-shot.\n\n```\nbefore: client → app handler → database server  (a server)\nafter:  client → app handler → S3/R2 bucket     (just storage)\n```\n\nThe trick is to cram the entire database execution layer inside an HTTP request. There is no server, daemon, or coordinator: each read or write runs as library code inside your request handler, the bucket holds the data, and the protocol supplies the commit rules.\n\nThe load-bearing operation is narrow — one conditional create of the next log object commits a write. When the request ends, baerly-storage is gone. Poof!\n\n**An API small enough to hold in your context.** No DDL, no raw SQL — 8 verbs and a ~12k-token surface you can hand to an LLM or a non-engineer and walk away.**Idle rounds to zero.** No database process to keep warm, and no per-app database floor across a fleet of small internal tools.**No data hostage.**`baerly export --target=postgres`\n\ngives you a per-collection SQL snapshot. Crossing the envelope is the graduation signal; the data exit is mechanical.**No new vendor.** Your S3/R2 bucket cleared security review years ago; every hosted alternative means a fresh vendor review and an IT ticket for a new managed-DB SKU.**Nothing to go down.** No resident service in your critical path, one fewer failure domain. Servers that don't exist can't go down.**Built like git.** Content-addressed documents, immutable numbered log entries, and one conditional log create as the commit, per collection.\n\n```\npnpm create @gusto/baerly-storage@latest\n```\n\nThe wizard asks for a project name, target, and starter, then prints the\ndev command. First run needs no bucket credentials: local dev uses local\nstorage and serves the UI plus `/v1/*`\n\nfrom one origin.\n\nFor a runnable multi-tab demo see\n[ examples/react-node/](/Gusto/baerly-storage/blob/main/examples/react-node); for the full set of\nproduction-shaped scaffolds see\n\n[.](/Gusto/baerly-storage/blob/main/examples)\n\n`examples/`\n\nThe public surface is a small document API. The scaffolds wire `db`\n\non\nthe server and `useQuery`\n\nin React; the calls look like this:\n\n```\n// server — writes land in your object-storage bucket\nawait db.collection(\"tickets\").insert({ title: \"Onboard Alex\", status: \"open\" });\n\n// client — reactive over your trusted handler, across every open tab\nconst open = useQuery((c) => c.collection(\"tickets\").where({ status: \"open\" }).all(), []);\n// open.status → \"loading\" | \"refreshing\" | \"ok\" | \"skipped\" | \"error\"\n// open.data is present for \"ok\" / \"refreshing\"\n```\n\nApplication auth and tenant choice stay explicit in the handler. What disappears is the database service and its surrounding machinery:\n\n```\n- docker-compose.yml\n- init.sql\n- prisma/schema.prisma\n- migrations/0001_initial.sql\n- RLS policies\n- DATABASE_URL secret\n- connection pool (pgbouncer)\n- pager rotation\n-\n+ // baerly.config.ts\n+ export default defineConfig({\n+   app: \"tickets\",\n+   tenant: \"main\",\n+   collections: { tickets: {} },\n+   target: \"cloudflare\",\n+   auth: \"none\", // dev; production supplies a verifier\n+ });\n```\n\nOrdinary schema shape changes are TypeScript or config edits — no DDL, no SQL strings, no generated migration ceremony.\n\n```\n// reads — Collection or, after a modifier, Query\ndb.collection(\"tickets\").get(id); // by id\ndb.collection(\"tickets\").where({ status: \"open\" }).all();\ndb.collection(\"tickets\")\n  .where((q) => q.gte(\"count\", 1))\n  .count();\n\n// writes — by id on Collection, bulk on Query\ndb.collection(\"tickets\").insert({ status: \"open\", title: \"ship it\" });\ndb.collection(\"tickets\").update(id, { status: \"closed\" }); // merge-patch\ndb.collection(\"tickets\").where({ status: \"closed\" }).delete();\n```\n\n| Surface | Vocabulary |\n|---|---|\nVerbs |\n`first` `all` `count` `get` · `insert` `update` `replace` `delete` |\nModifiers |\n`where` `order` `limit` |\nOperators |\n`eq` `gt` `gte` `lt` `lte` `in` |\nErrors |\none `BaerlyError` , discriminate by `.code` (`Conflict` , `NotFound` , `SchemaError` , …) |\n\nFull reference: [ docs/guide/cheatsheet.md](/Gusto/baerly-storage/blob/main/docs/guide/cheatsheet.md),\nor\n\n`cat node_modules/@gusto/baerly-storage/dist/API.md`\n\nin an installed\napp.**The hard part of a database-in-a-bucket is the commit.** A bucket can\nstore objects; it cannot run a transaction coordinator. So one writer must\nwin each race, and every reader must be able to tell what won. [S3's strong consistency](https://aws.amazon.com/blogs/aws/amazon-s3-update-strong-read-after-write-consistency/) makes object\nstorage usable as shared state; conditional writes supply the\none-writer-wins operation.\n\nConcretely, a write drops new immutable objects in the bucket and then\ncreates the next numbered log entry for that collection with\ncreate-if-absent (`If-None-Match: \"*\"`\n\n). Two writers racing the same slot\ncannot both win; the loser reads the winner and retries at the next slot.\nThat create *is* the commit. There is no resident coordinator: each\nrequest reads bucket state, tries that create, and leaves no required\nprocess behind. A read follows `current.json`\n\nto the snapshot and folds\nthe committed log tail into rows.\n\nbaerly-storage shares the immutable-artifact foundation of table formats\nlike Apache Iceberg and Delta Lake, but commits with a narrower step: no\nmetadata-pointer swap and no separate coordinator — just that one log\ncreate. See [prior art and lineage](/Gusto/baerly-storage/blob/main/docs/spec/prior-art.md) for how it\nrelates to Iceberg, Delta Lake, Litestream, and Turbopuffer.\n\nEach collection has its own ordered log, so **writes are per-collection\nlinearizable** — the `If-None-Match`\n\nlog create linearizes every commit.\nCross-collection writes are unordered and non-atomic; that boundary is\npart of the contract (see [When (not) to use it](#when-not-to-use-it)).\n\nThe durable contract is the bucket layout plus the conditional-write\nrules. Another language could speak it by writing the same layout and\nhonoring the same rules. See\n[ storage-compatibility.md](/Gusto/baerly-storage/blob/main/docs/spec/storage-compatibility.md).\n\nBucket credentials never leave the server. Browsers talk only to your\ntrusted handler, which authenticates the caller, chooses the tenant\nprefix, and applies the protocol against the bucket. Production recipes\nsupport Cloudflare Access and JWKS bearer verification; shared-secret\nauth is for service-to-service calls and dev. See\n[ client-auth.md](/Gusto/baerly-storage/blob/main/docs/guide/client-auth.md).\n\nBefore you count rows or price reads, ask one question:\n\nCan the app's most important work be done from one collection?\n\nIf yes, baerly-storage may fit; then check query shape, atomicity, size,\nand cost. A todo list, a single board's kanban, an event's RSVPs, one\nchannel's chat — each maps to one collection. The shape is narrow on\npurpose: production-shaped for small workloads with a specific access\npattern, not a general-purpose database. If the core screen is a view\n*across* many collections, users, or tenants (\"my pull requests,\" \"all\ncode search,\" a cross-org dashboard), baerly-storage\nshould not be the only query engine for it.\n\nIt is **deliberately not** a few things:\n\n**No SQL, no joins.** Equality + dotted-path predicates, operators added one at a time. The small surface is part of the contract.**Not a D1 / Postgres replacement.** Those are graduation targets, not competitors — baerly-storage keeps the experiment cheap until you know whether it's worth graduating.**Browser-direct multi-writer is out.** Trusted server-side app code is the design center.**Realtime is long-poll first.** Polling is always correct; a WebSocket tier would be a future opt-in.\n\nNone of these are apologies — baerly-storage names its envelope so\ngraduation is a feature, not a surprise: `baerly export --target=postgres`\n\nmakes the exit mechanical.\nThe shape test lives in\n[workload-fit.md](/Gusto/baerly-storage/blob/main/docs/about/workload-fit.md); the numeric envelope in\n[graduation.md](/Gusto/baerly-storage/blob/main/docs/about/graduation.md).\n\n- ✨\n**Why baerly-storage**—`docs/about/why-baerly.md`\n\n- 🧭\n**How it works**—(bucket + conditional log create)`docs/about/how-it-works.md`\n\n- 🧱\n**Product thesis**—`docs/about/thesis.md`\n\n- 🏗️\n**Architecture**—`docs/architecture.md`\n\n- 🔧\n**Embed by hand**—(embed-by-hand + custom-routes recipes; ships as`packages/server/API.md`\n\n`dist/API.md`\n\nin the package) - 📊\n**Alternatives**—(how baerly-storage compares to Firebase, Supabase, Convex, and Cloudflare D1)`docs/about/alternatives.md`\n\n— agent + contributor entry point (the fastest map for humans too).`CLAUDE.md`\n\n`AGENTS.md`\n\nis a symlink.— topic map: architecture, conventions, ADRs, protocol specs, operating procedures.`docs/README.md`\n\n— runnable scaffolds + the`examples/`\n\n`react-node/`\n\nmulti-tab demo.", "url": "https://wpnews.pro/news/show-hn-baerly-storage-a-document-db-that-runs-per-request-no-db-server", "canonical_source": "https://github.com/Gusto/baerly-storage", "published_at": "2026-07-07 16:44:03+00:00", "updated_at": "2026-07-07 16:59:52.542984+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools"], "entities": ["S3", "R2", "PostgreSQL", "Cloudflare", "Git"], "alternates": {"html": "https://wpnews.pro/news/show-hn-baerly-storage-a-document-db-that-runs-per-request-no-db-server", "markdown": "https://wpnews.pro/news/show-hn-baerly-storage-a-document-db-that-runs-per-request-no-db-server.md", "text": "https://wpnews.pro/news/show-hn-baerly-storage-a-document-db-that-runs-per-request-no-db-server.txt", "jsonld": "https://wpnews.pro/news/show-hn-baerly-storage-a-document-db-that-runs-per-request-no-db-server.jsonld"}}