Product Docs Pricing Changelog
Start free Sign in
Docs / Getting started / Architecture

Architecture: server-first by design

Read this before you write code, especially if you're coming from Supabase. The flarelink.from(…) builder looks familiar, but the trust model is deliberately different.

The one-paragraph version. The browser can call flarelink.auth.* only. Database and storage require the service key and run on your server. There is no row-level security yet — your server route is the authorization boundary, so scope every query to the signed-in user (where: { author_id: me.id }). If you've been leaning on Supabase RLS to let the browser query the DB directly, that pattern doesn't exist here.

Two planes: control vs. data

Flarelink is a control plane. The dashboard talks to Cloudflare's REST API with your token to create and configure resources — a D1 database, a KV namespace, an R2 bucket, your auth Worker — and then walks away. Everything it provisions lives on your Cloudflare account.

Your app's runtime traffic is the data plane, and Flarelink is never in it. Your users hit your auth Worker (on your account) for sign-in; your app reads its own D1; browsers upload straight to your R2. If Flarelink disappeared tomorrow, your deployed app keeps running unchanged. (This is also why "leaving is a non-event" — see Trust & verification.)

  • Control plane (Flarelink): provisioning, the table editor, the SQL console, config writes. Uses your CF API token.
  • Data plane (your account): the auth Worker, D1, KV sessions, R2. Flarelink never proxies this.

Who can call what

Surface Browser Server (with service key)
flarelink.auth.*✓ yes✓ yes (forward cookies)
flarelink.from(…) / .sql\`…\`✗ no✓ yes
flarelink.storage.*✗ no (bytes go browser → R2 via presigned URL)✓ yes

The service key grants full DB + R2 access for the project. Constructing a client with it in the browser would leak it — so flarelink.from(…) / flarelink.storage.* throw MissingServiceKeyError if you forget the key, and you should never put the key in a client bundle. Auth is the exception: it's safe everywhere because the session cookie, not a static secret, is the credential.

No row-level security (yet)

Supabase lets the browser query Postgres directly because RLS policies enforce per-row access in the database. Flarelink has no equivalent today, which is exactly why database access is server-only: with no in-database policy layer, the only safe place to enforce "user A can't read user B's rows" is your own server code.

The practical rule:

// ✓ authorize on the server, scope to the signed-in user const me = await flarelink.auth.getMe() // cookie-derived identity if (!me) throw new Response("Unauthorized", { status: 401 }) const { rows } = await flarelink .from("posts") .where({ author_id: me.id }) // never trust an id from the client // ✗ don't: take an id from the request body and query it unscoped

Browser-side queries with row-level security policies are on the roadmap; until then, treat the server route as the boundary. Each framework's protect-a-route recipe shows the pattern.

Sessions live in KV

The auth Worker stores sessions in KV, never D1 — auth checks happen on every request, and KV reads are sub-millisecond and cheap, whereas a D1 row-read per request adds up. One consequence: KV is eventually consistent, so a just-signed-out session can validate for a short window at a far edge. See Limits & semantics for the details.

What this buys you

  • No lock-in. Everything runs on your Cloudflare account at Cloudflare's prices. Flarelink takes no cut and isn't a dependency at runtime.
  • No data-processor relationship. Your users' data never touches Flarelink's servers — it's in your D1 and your R2.
  • Verifiable auth. The auth Worker is source-available and you can confirm the deployed bundle byte-for-byte — see Trust & verification.
Something unclear or missing? [email protected] llms-full.txt ↗