In the browser

The whole backend, in a tab

Every tinbase service is a pure (Request) ⇒ Response fetch handler. There is no HTTP layer to stand up — hand the handler to supabase-js as its fetch and the database, auth, storage, and realtime all run in-process, inside the page. No server. No network round-trip.

Run it in-process

The same supabase-js you use everywhere, pointed at a backend that lives in the same JavaScript heap:

import { createClient } from '@supabase/supabase-js'
import { createBackend, createPgmemEngine } from 'tinbase'

// a whole backend — in memory, inside the page
const backend = await createBackend({
  engine: await createPgmemEngine(),   // pure JS, nothing to download
  migrations: [{
    name: 'init',
    sql: 'create table todos (id serial primary key, text text, done boolean default false)',
  }],
})

// hand it to supabase-js as a custom fetch — no server, no network
const supabase = createClient('http://localhost', backend.anonKey, {
  global: { fetch: (input, init) => backend.fetch(new Request(input, init)) },
})

await supabase.from('todos').insert({ text: 'ship it' })
const { data } = await supabase.from('todos').select()

Pick an engine for the browser

Two of the three engines run in a browser. pg-mem is pure JavaScript and the lightest thing to embed; PGlite is real Postgres compiled to WASM when you want full fidelity.

pg-memPGlite (wasm)
Footprint~6.7 MB install · pure JS · no WASM~575–650 MB heap · WASM
FidelityCRUD, auth, functions, realtime, webhooks + PL/pgSQL, triggers, RLS policiesfull Postgres — enforced RLS, extensions
Persistencein-memoryIndexedDB / OPFS
Best forphones, previews, the lightest embedfull Postgres parity in the browser

pg-mem now runs PL/pgSQL, triggers and RLS-policy DDL (via the @tinbase/pg-memfork), but as a superuser so RLS isn't enforced per-request, and cron/pgmq are absent — it's meant for local dev and previews. For full, enforced Postgres semantics in the browser, use PGlite — and persist across reloads with an IndexedDB data dir:

import { createBackend } from 'tinbase'

// omit `engine` → PGlite (real Postgres in WASM).
// persist across reloads with an IndexedDB-backed data dir:
const backend = await createBackend({ dataDir: 'idb://my-app' })

How it fits together

The same fetch handler serves an HTTP + WebSocket server in Node, or runs directly in the page in the browser — only the transport changes.

@supabase/supabase-jsthe official SDK, unmodifiedRESTAuthStorageRealtimeFunctionssame wire protocol — HTTP in Node · in-process fetch in the browsertinbaseone (Request) ⇒ Response fetch handlerPostgREST/rest/v1GoTrue/auth/v1Storage/storage/v1RealtimeWebSocketEdge Fns/functions/v1Studio/_/DbEngine adapter · query · exec · transaction · listentinbase (wasm)PGlite — Postgres in WASMtinbase (native)embedded Postgres 17tinbase (pg-mem)pure JS, in-memoryreal Postgres semantics — RLS, triggers, FKs, jsonb (pg-mem is a subset)In NodeHTTP + WebSocket server — one process, no DockerIn the browserin-process fetch handler — the whole backend in a tab

Why it was built this way

tinbase came out of lifo — a project that maps Linux APIs into the browser — and RapidNative, where Expo apps run full-stack in the browser and on phones. Both need a real backend with no server behind it, which is exactly why every service here is a fetch handler and the database can be pure JavaScript.

Embedding docsSee the Studio →