Ramose — the typed, realtime database for Cloudflare
Built for React apps on Cloudflare
You know React and TypeScript. You want a backend you don’t have to write.
Typed end to end
The schema is a TypeScript file; a wrong write is a red squiggle, not a bad row.
Realtime without plumbing
useLive(db, query); the screen updates when the data does. Reef has no refetch code.
Permissions in the database
Who may read or write each field, checked on the server, deny by default.
A database per customer
ramose.db(“acme”) is a function call, not a provisioning step.
The schema is TypeScript
Reef’s whole data model is one file. Fields have types, references point at other records, and a list is just cardinality: “many”. Your app, your rules and your deploy import it — no codegen, no migration step.
export const Issue = Ramose.Namespace("issue", { title: Ramose.Attr(Schema.String), status: Ramose.Attr(Schema.String), priority: Ramose.Attr(Ramose.Long), // … assignee: Ramose.Attr(Ramose.Ref(() => User)), labels: Ramose.Attr(Ramose.Ref(() => Label), { cardinality: "many" }), privateNote: Ramose.Attr(Schema.String, { doc: "visible to the admin class only", }),});// issue.add(Issue.priority, "high") does not compile:// priority is a number.One query, always current
The board is one query, read with useLive. Drag a card in one window and every other window moves it too — no WebSocket code, no refetch anywhere in Reef.
Ramose is built on Effect. In React you rarely see it — the hooks run it for you. Effect in five minutes →
src/app/screens/BoardScreen.tsx:230-234 export const boardQuery = Ramose.query(Issue) .orderBy(Issue.rank, "asc") .select(boardShape);// …const db = useDb(slug, Reef);const board = useLive(db, boardQuery);Permissions are part of the database
One policy says who may read and write each field. The buttons are just polite — the server refuses the write.
Because the policy and the shapes your screens read are both values, Ramose.Policy.compile(policy, { pulls }) checks them against each other at deploy time — tighten a rule a screen depends on and the deploy fails, rather than that screen quietly emptying for one customer.
Ramose verifies sign-in tokens; it never issues them. Bring Better Auth (Reef does), Clerk, Auth0, WorkOS — any provider that publishes signing keys.
issue: { read: P.allow(anyone), create: P.allow(editor), add: P.allow(ownIssue), retract: P.allow(ownIssue), retractEntity: P.allow(ownIssue), preset: [P.preset(Issue.creator, P.principal)], attrs: [ // Only admins receive this field; the server strips it for others. P.attr(Issue.privateNote, { read: P.allow(admin) }), ],},
src/app/screens/BoardScreen.tsx:241-245 Also built in
One database per customer, for free
Typing a workspace name in Reef creates a fresh, isolated database from the browser. No deploy, no migration, no per-customer server.
src/app/mutations.ts:33-36 Nothing is overwritten
Drag the slider and the board becomes what it was after write number t. Same query, one extra argument, nothing copied.
src/app/screens/BoardScreen.tsx:448-453 Instead of what?
The honest trade against what you’d use otherwise:
| Instead of | Same idea | What’s different, and what you give up |
|---|---|---|
| Convex | queries that re-run themselves | runs in your Cloudflare account; a database per customer is a function call. No hosted dashboard, scheduled functions or file storage. |
| Supabase | rules enforced by the database | rules per field, deny by default, checked against your screens at deploy. No SQL; bring your own sign-in and file storage. |
| Instant / Firebase | read and write from the browser, it stays live, per-user rules | typed schema in TypeScript; one deploy you own. No console, no managed infra, no offline SDK. |
| D1 + Drizzle, or a Durable Object + SQLite | the same Cloudflare primitives, your account | already assembled: live fan-out, per-customer routing, permissions, history. You give up SQL. |
| Postgres + an ORM | typed access | no migration rewrites old data; live queries and history come standard. No SQL, no planner, no cross-database joins. |
What Ramose does not have: SQL, or joins across databases · a hosted dashboard · its own sign-in · file storage · scheduled jobs.
How it works
Three parts, one deploy.
One writer per database
Writes commit in order; no conflicts, no “eventually”.
Storage keeps every version
A write is durable before your call returns; nothing is rewritten, so yesterday is still readable.
Reads run next to your users
On copies, so a slow read never blocks a write.
Pre-release·no Ramose bill: it runs in your Cloudflare account, so you pay Cloudflare for what you use·669 tests run on every pull request and on master·built in the open — GitHub
Try it in two lines
Install it, describe your data, watch every tab keep up.
mkdir my-todos && cd my-todosbun add ramose react react-domFifteen minutes from an empty folder to a realtime todo app running on your laptop — no Cloudflare account needed. Getting started writes every file with you. Want to read a finished app instead? Reef is an issue tracker in 680 lines.