Drop-in
Works with Next.js, Express, NestJS, Hono, Elysia, Fastify, Bun. No rewrite.
Bidirectional sync between Postgres and any client. Offline-first. One package. Works with your existing Next.js + Drizzle/Prisma + tRPC.
pnpm add bettersyncWorks with Next.js, Express, NestJS, Hono, Elysia, Fastify, Bun. No rewrite.
pnpm add bettersync. 9 subpath exports. Everything included.
PGlite in browser, Postgres on server. Same SQL dialect, same adapter pattern.
Writes go to local store first. Sync happens in background when online.
Hybrid Logical Clock + Last-Write-Wins. Deterministic across all clients.
Per-model scope functions. Client A never sees client B data. Enforced server-side.
SyncProvider, useSync, useSyncQuery with live mode. SyncDevtools built in.
npx bettersync init scaffolds your project. generate creates SQL migrations.
19-test conformance suite. Drizzle + PGlite adapters tested against real Postgres.
import { betterSync } from 'bettersync'import { drizzleAdapter } from 'bettersync/adapters/drizzle'export const sync = betterSync({ database: drizzleAdapter(db, { schema: { project: projects }, }), models: { project: { fields: { id, userId, title, changed }, scope: ctx => ({ userId: ctx.userId }), }, },})import { sync } from '@/lib/sync'export const POST = sync.handlerimport { createSyncClient } from 'bettersync/client'import { pgliteAdapter } from 'bettersync/adapters/pglite'export const syncClient = createSyncClient({ database: pgliteAdapter( new PGlite('idb://app'), ), schema: syncSchema, syncUrl: '/api/sync',})| bettersync | ElectricSQL | Zero | PowerSync | |
|---|---|---|---|---|
| Drop into existing stack | ✅ Yes | ❌ TanStack Start | ❌ Own data layer | ⚠️ Managed service |
| Extra infrastructure | ✅ None | HTTP/2 + Caddy | zero-cache | PowerSync service |
| Install | ✅ 1 package | Scaffolded app | 4+ packages | 3+ packages |
| Local DB | PGlite / SQLite | PGlite | Custom store | SQLite |
| Conflict resolution | HLC LWW | CRDT shapes | Server authority | Custom |
| License | Apache 2.0 | Apache 2.0 | MIT | Apache 2.0 |
One handler. Standard Web API. Every framework supported.
bettersyncCore + betterSync() + server + client
bettersync/clientLocal-first sync client engine
bettersync/serverHandler + hooks
bettersync/next-jsNext.js App Router handler
bettersync/nodeExpress / Fastify / NestJS adapter
bettersync/reactSyncProvider, useSync, useSyncQuery, SyncDevtools
bettersync/adapters/drizzleDrizzle + Postgres (better-auth style)
bettersync/adapters/prismaPrisma + Postgres
bettersync/adapters/kyselyKysely + Postgres
bettersync/adapters/pgRaw node-postgres
bettersync/adapters/pglitePGlite (Postgres WASM in browser)
bettersync/adapters/better-sqlite3SQLite for Node.js / Electron
bettersync/test19-test conformance suite
Copy this prompt into Claude, Cursor, or Copilot. It will analyze your project, ask what to sync, and generate all the code.
You are integrating bettersync into an existing project. bettersync is a local-first bidirectional sync engine for TypeScript. It syncs data between a server (Postgres) and clients (browser via PGlite, Node via SQLite).
Documentation: https://bettersync.vercel.app/docs
npm: https://www.npmjs.com/package/bettersync
GitHub: https://github.com/beautyfree/bettersync
## Your task
1. **Analyze the project** — detect the framework (Next.js, NestJS, Hono, Express, Fastify, Elysia, Bun), package manager, ORM (Drizzle, Prisma, or none), existing database, auth solution, and directory structure (src/ or not, App Router vs Pages Router for Next.js).
2. **Ask the user:**
- Which models/tables should be synced? (e.g. "projects and tasks")
- What is the scope field for multi-tenancy? (e.g. "userId" — each user sees only their data)
- Should the client use PGlite (browser Postgres WASM) or memory adapter (for prototyping)?
- What auth method is used? (JWT, session, Clerk, better-auth, etc.)
3. **Install bettersync:**
```bash
pnpm add bettersync
# If using PGlite for browser client:
pnpm add @electric-sql/pglite
```
4. **Create the sync config file** (e.g. `lib/sync.ts` or `src/lib/sync.ts`):
```ts
import { betterSync } from 'bettersync'
// Import your adapter (drizzleAdapter for Drizzle, memoryAdapter for prototyping)
export const sync = betterSync({
database: yourAdapter,
models: {
// Define each model the user wants to sync with:
// fields (id, userId, title, changed — HLC field required)
// scope function for multi-tenancy
},
auth: async (req) => {
// Extract userId from the request using the project's auth solution
},
})
export const syncSchema = sync.schema
```
5. **Mount the sync handler:**
- Next.js App Router: `app/api/sync/route.ts` → `export const POST = sync.handler`
- Next.js Pages Router: `pages/api/sync.ts` → use `parseSyncRequest` + `handleSync`
- Express/NestJS/Fastify: `import { toNodeHandler } from 'bettersync/node'` → `app.post('/api/sync', toNodeHandler(sync))`
- Hono: `app.post('/sync', (c) => sync.handler(c.req.raw))`
- Elysia: `app.mount('/sync', sync.handler)`
6. **Create the client config** (e.g. `lib/sync-client.ts`):
```ts
import { createSyncClient } from 'bettersync/client'
import { pgliteAdapter } from 'bettersync/adapters/pglite'
import { PGlite } from '@electric-sql/pglite'
import { syncSchema } from './sync'
export const syncClient = createSyncClient({
database: pgliteAdapter(new PGlite('idb://app-name')),
schema: syncSchema,
syncUrl: '/api/sync', // or full URL for cross-origin
headers: () => ({ // if auth requires headers
Authorization: \`Bearer \${getToken()}\`,
}),
})
```
7. **Generate database migration:**
```bash
npx @bettersync/cli generate --config lib/sync.ts
# For existing tables:
npx @bettersync/cli generate --config lib/sync.ts --alter --backfill
```
8. **If React** — wrap the app with SyncProvider:
```tsx
import { SyncProvider, SyncDevtools } from 'bettersync/react'
// Use dynamic import for PGlite to avoid SSR issues in Next.js
// Use useSyncQuery((s) => s.model('todo').findMany(), [], { live: true }) for reactive queries
```
## Important details
- The `changed` field (HLC) MUST be declared in every model's fields. It's how sync tracks versions.
- The Drizzle adapter accepts Drizzle table objects directly: `drizzleAdapter(db, { schema: { project: projectsTable } })`. Column mapping is automatic.
- PGlite in Next.js MUST be dynamically imported (client-only) to avoid SSR crashes.
- `sync.handler` is a standard Web API `(req: Request) => Promise<Response>` handler.
- `toNodeHandler(sync)` converts it for Express/Fastify/NestJS.
- For live reactive queries use `useSyncQuery(fn, deps, { live: true })` — auto-refetches on every local write and sync.
- `sync.on('change', callback)` for event-driven updates outside React.
## After setup
Tell the user to open the app in two browser tabs, make a change in one tab, and verify it appears in the other. That's the magical moment.From zero to two-tab live sync in under 2 minutes.