Build your own stack
Ferry is not a platform to move your app onto, and not a React library either: the client is plain TypeScript, for any JavaScript app. It is a set of pieces, and each one is a plug point with a small contract. Keep the database, the auth, the framework and the state library you already have; Ferry adds the sync between them.
The pieces
| Piece | What Ferry asks of it | Built in | Your own |
|---|---|---|---|
| Where the app reads | get() and subscribe() on a query, sync.onStatus | React hooks, Zustand, Jotai, Legend-State | Vue, Svelte, Solid, vanilla JS: your own bindings, in a few lines |
| Where the device keeps data | Three methods: load, write and clear | IndexedDB, SQLite, MMKV, localStorage, memory | Write a storage, proven by storageConformanceCases |
| Who the user is | auth(request): a user id, or null | None: it is always yours | Any session, cookie or token: Supabase, Firebase, Clerk, Better Auth, your own |
| Where the server runs | fetch(Request), or toNodeHandler for Node's http | Any framework that speaks Request and Response | Next.js, Hono, Express, NestJS, Fastify, Bun, Deno, Workers |
| Where the server keeps data | A ServerStore: transactions, rows and an ordered log | Postgres and SQLite (pg, postgres.js, PGlite, Prisma, Drizzle, node:sqlite, bun:sqlite, better-sqlite3, a Durable Object's SQLite), and Cloudflare D1 | Another database, proven by serverStoreConformanceCases from ferrysync/testing |
Each contract is small on purpose. The two with the most to get right, the device's storage and the server's store, come with a conformance kit: the same tests Ferry's own pass. A piece that passes them works with the rest.
Your stack, as a backend for your frontend
The usual shape is a backend for frontend: the app talks only to its local database, and your backend sits between the devices and the services you already pay for. Ferry runs inside that backend, on one route.
- Supabase: Supabase's Postgres and Supabase Auth, with Ferry in your own route.
- Firebase: Firebase Auth for the users, your own database for the synced data.
Your own pieces
When nothing built in fits, write the piece. Each guide shows the contract, a real implementation, and how to prove it:
- Your own database: a driver for any SQL library (libSQL and Turso here), or a store for any database.
- Your own auth: sessions, any JWT provider, the token on the device.
- Your own bindings: Svelte, Vue, or any library that can subscribe.
- Your own storage: where the device keeps its data.
Backends
The same server, mounted where your app already runs:
- Next.js: a route handler of the same app, and on Vercel.
- Hono: one line, on Node, Bun, Workers or Deno.
- Express:
toNodeHandler, body parsers and auth. - Node.js:
node:httpwith nothing else, Fastify and Nest. - Bun:
Bun.serveandbun:sqlite. - Cloudflare Workers: D1 through a Durable Object, or Postgres with a store per request.
Data that is not synced
Next to Ferry, for what lives only on a server:
A runnable app
The repository's examples/ folder holds a complete todo app: todo-server (Hono, Bun and
bun:sqlite), todo-web (Vite and React) and the todo-schema they share. Run both with
bun run example from the repository root.