Backend only (headless)
Rebase has two shapes, and this page is the one that never opens a browser: a REST API, auth, storage, realtime and backups over a PostgreSQL database you already have. No admin panel, no collection files. If you were reaching for Supabase or PostgREST, this is the comparable thing.
Everything on this page also works in the full project — it is the same server.
What --headless removes is the frontend package and the collection files, not
a capability.
Scaffold it
Section titled “Scaffold it”pnpm dlx @rebasepro/cli init my-api --headless --yescd my-apiTwo workspaces, no frontend/:
| Folder | What is in it |
|---|---|
backend/ |
Your custom functions and crons. There is no server file — the published runtime boots the project |
config/ |
storageAuthorize, and any collections --introspect generates |
--template has no effect here: a preset seeds collection files, and this
flavour has none. Node 22.22+, the same floor as the full project — the headless
overlay’s package.json declares "node": ">=22.22.0" and replaces the one
under it.
Point it at your database
Section titled “Point it at your database”init generates a ready-to-run .env. To use a database you already run, pass
its URL at scaffold time:
pnpm dlx @rebasepro/cli init my-api --headless --database-url "postgres://user:pass@host:5432/db" --yesOr set DATABASE_URL in .env afterwards — it is the same thing. With no
DATABASE_URL, rebase dev starts a managed PostgreSQL (PGlite) in the project
directory, which is useful for trying the API out but is not what this flavour
is for.
Then:
pnpm installpnpm run devRead the URL from the output. rebase dev derives a free port from the
project’s path rather than using a fixed one, so it differs between projects and
between machines.
Where the collections come from
Section titled “Where the collections come from”There are none in code. The server reads your database schema at boot and serves the tables it finds, so the API follows your migrations: change the schema, and the endpoints change with it.
A table is served once row-level security is enabled on it. With no policy it is served and returns no rows, and boot warns about it; a policy opens it:
ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;CREATE POLICY your_table_owner ON your_table FOR ALL USING (user_id = rebase.uid());rebase.uid(), rebase.roles() and rebase.jwt() are installed by Rebase and
read the identity of the authenticated request. See
Security Rules for the policy vocabulary,
and rls-check for an audit of what your policies actually
allow.
A table without RLS is skipped, deliberately: every authenticated request
runs as rebase_user, so serving a table that row-level security does not guard
would hand every row to every signed-in caller. Each skipped table is named at boot along with the SQL
that would protect it.
Generating collection files instead
Section titled “Generating collection files instead”If you would rather have the tables written down as TypeScript — for types, for callbacks, for review — introspect them:
pnpm dlx @rebasepro/cli init my-api --headless --database-url "postgres://…" --introspect --install--introspect implies --template blank and needs --install, because it runs
against the installed CLI. In an existing project the same thing is:
pnpm rebase schema introspectFiles land in config/collections/. From that point the project has collections
in code and the boot-time introspection stops being what defines the API.
Since 0.25 The headless
scaffold installs Atlas in backend/, so rebase db push, rebase db generate
and pnpm run db:migrate work on those collections as in any project. On 0.24
each of them stops on “The atlas binary is missing”; add it to the backend with
pnpm --dir backend add @ariga/atlas.
Use it
Section titled “Use it”Over HTTP:
curl "$REBASE_URL/api/data/posts?limit=10"Or with the type-safe client, which is a dependency of the headless scaffold already:
import { createRebaseClient } from "@rebasepro/client";
// The URL `rebase dev` printed, or your deployment's. `pnpm example` reads it// from `.rebase-dev-url` when the variable is unset.const rebase = createRebaseClient({ baseUrl: process.env.REBASE_URL! });
const { data: posts } = await rebase.data.collection("posts").find({ where: { published: ["==", true] }, limit: 10});- REST API — the endpoint shapes, filters and errors
- Typed SDK — querying, auth, realtime, storage
/api/docsand/api/swagger— the OpenAPI document and its viewer, served by the running backend once it has a collection. A project with none serves neither: the document is generated from the collections, so there is nothing to describe until the section above has run
404 NO_COLLECTIONS
Section titled “404 NO_COLLECTIONS”If every data request answers with this:
{ "error": { "message": "This project serves no collections yet. …", "code": "NO_COLLECTIONS" }}then the project declares no collections in code and the database gave it nothing to derive them from. It is the expected first response from a headless project pointed at an empty database, and it is a 404 rather than a 500 because nothing is broken — there is simply nothing to serve yet.
Three things resolve it, in the order worth checking:
- The database has no tables. Create them — a migration, plain SQL, or a
collection file plus
rebase db push— and restart. - The tables do not have row-level security enabled, so boot skipped
them. The boot log names each one, with the SQL. Enable it and add a policy,
as above. Since 0.25 The
response says so too: its message names the tables and gives the
ALTER TABLE … ENABLE ROW LEVEL SECURITYfor them, anddetails.unprotectedTableslists them. On 0.24 it says the database has no tables. DATABASE_URLpoints somewhere else than you think.rebase statusprints the three files that decide what the backend reaches.
Adding an admin panel later
Section titled “Adding an admin panel later”Nothing here locks you out of it. Add a config/collections/ directory — by
hand or with rebase schema introspect — and a frontend that renders them; the
backend does not change. Frontend Setup is where that starts.
Next steps
Section titled “Next steps”- Authentication — providers, tokens, API keys
- Security Rules (RLS) — the access model
- Custom Functions — your own routes
- Deployment — taking it to production