The backend,
on its own.
Point it at a Postgres database and you get REST, a typed SDK, realtime over WebSocket, auth, storage and an OpenAPI spec — with every access rule enforced by the database itself. No admin panel required. No React in the dependency tree.
01·REST, without writing it
Every table you expose becomes an endpoint
No controllers, no serializers, no validation middleware. Filtering, sorting, pagination and relation loading are query parameters, in a PostgREST-compatible syntax your team probably already knows.
Nested relations are addressable as paths, so a customer's orders are a URL rather than a join you hand-wrote.
GET /api/data/orders?status=eq.paid&total=gt.100
GET /api/data/orders?or=(status.eq.paid,total.gt.500)
GET /api/data/orders/42?include=customer,items
GET /api/data/customers/7/orders
POST /api/data/orders
PUT /api/data/orders/42
DEL /api/data/orders/4202·The typed client
Autocomplete that comes from your schema
rebase generate-sdk turns your collections into a Database type. Pass it to the client and collection names, field names, filter operators and return shapes are all checked at compile time — including the fields you asked for in include.
Rename a column in the collection and the call sites turn red before anything ships.
The full SDK tour// generated once by `rebase generate-sdk`
import type { Database } from "./generated/sdk/database.types";
const client = createRebaseClient<Database>({
baseUrl: "https://api.example.com",
});
// collection names, field names and return types all autocomplete
const { data } = await client.data.orders
.find({
filter: { status: ["eq", "paid"] },
include: ["customer"],
limit: 50,
});03·Realtime
Changes arrive, however they were made
listen() on any collection and every insert, update and delete lands on the client over WebSocket. Subscriptions respect the same row-level security as a read, so a subscriber is never pushed a row they could not have fetched.
Writes made outside the API count too — a row changed frompsql, a cron job or Studio's SQL editor emits the same event, because change capture happens in the database. Across replicas, fan-out rides Postgres LISTEN/NOTIFY.
04·Authorization
The rules live in Postgres, not in this server
Requests execute as a restricted database role. Your securityRulescompile to real row-level security policies, so a query that should return nothing returns nothing — whether it arrives through the SDK, a REST call, the admin panel, an agent's API key or a psql session. A collection with no policy serves no rows: the default is closed, not open.
export const orders = {
slug: "orders",
table: "orders",
securityRules: [
// customers see their own orders…
{ operation: "select", ownerField: "customer_id" },
// …support can read and update all of them
{ operations: ["select", "update"],
roles: ["support"] },
],
};-- rebase schema generate → db push
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY orders_select_1f4c9ab ON orders
FOR SELECT TO rebase_user
USING (customer_id = rebase.uid());
CREATE POLICY orders_update_8b02e13 ON orders
FOR UPDATE TO rebase_user
USING (string_to_array(rebase.roles(), ',')
&& ARRAY['support']);
-- no policy for INSERT or DELETE:
-- nobody can insert or delete. Closed by default.npx @rebasepro/rls-check $DATABASE_URLaudits it without installing anything.05·One server
Everything mounted, nothing assembled
This is the routing table of a default project — one process, one deployment, no service mesh to draw. Each of these exists because building it yourself is a week you would rather spend elsewhere.
Webhooks. HMAC-signed outbound calls on insert, update and delete, with retries.
Entity history. An audit trail per collection: who changed what, when, and a revert.
Database branching. Isolated copies of the whole database for a feature or a test run.
Email. SMTP for verification, password reset and your own transactional templates.
Backups. Role-complete dumps and restores, driven from the CLI or the admin API.
Multiple sources. Route different collections to different databases from one server.
/api/data/:collectionCRUD, filters, sorting, pagination, relation includes, nested subcollections/api/auth/*sign-up, login, refresh, password reset, OAuth, MFA, roles/api/storage/*upload, download, signed access — local, S3-compatible or GCS/api/functions/:nameyour own Hono routes, auto-mounted from functions//api/cron/*scheduled jobs with run history and manual triggers/api/docs · /api/swaggerOpenAPI 3.0 spec, plus an explorer in dev/api/metathe runtime contract: collections, properties, capabilities/healthreadiness for your orchestrator — asserts the auth schema, not just TCPWebSocketrow subscriptions, broadcast channels and presence on the same server
06·Underneath
Boring technology, on purpose
A Hono app and a Drizzle schema. Two things your team can read, debug and extend without learning a framework — and without waiting for us to expose a hook.
import { initializeRebaseBackend } from "@rebasepro/server";
import { createPostgresBootstrapper } from "@rebasepro/server-postgres";
const backend = await initializeRebaseBackend({
server,
app,
bootstrappers: [
createPostgresBootstrapper({
connection: db,
schema: { tables, enums, relations },
})
],
auth: { jwtSecret: process.env.JWT_SECRET },
storage: { type: "local", basePath: "./uploads" },
});
// it's a Hono app — mount it, wrap it, add your own middleware- ✓ Mount it as a sub-app on a server you already run
- ✓ Your middleware, your routes, your error handling
- ✓ Node, Docker, Railway, Fly.io or bare metal
// one collection, one relation
const posts = {
slug: "posts",
properties: { title: { type: "string" } },
relations: [{
relationName: "author",
target: () => users,
cardinality: "one-to-one"
}]
};
// GET /api/data/posts?include=author
// → one query via db.query.findMany, not 1 + N- ✓ One-to-one, one-to-many and many-to-many
- ✓ Field selection — fetch the columns you asked for
- ✓ Cursor and offset pagination
07·The other half
Headless by default
The admin panel is a separate product. Toggle it on below: the project gains one dependency and a nested admin block, a back office appears — and the API response on the right does not move.
A headless project: no React, no admin block, no panel.
Your project
Dependencies
- @rebasepro/server
- @rebasepro/server-postgres
- @rebasepro/client
Without admin.d.ts, an `admin` key on a collection is a type error.
config/collections/users.ts
export const users = {
name: "Users",
table: "users",
properties: {
email: { type: "string" },
displayName: { type: "string" },
},
securityRules: [
{ operation: "select",
using: "id = rebase.uid()::uuid" },
], admin: {
icon: "Users",
group: "Settings",
listProperties: ["displayName", "email"],
},};
The server loads this file either way — and never reads inside admin.
Your API
identical[{ "id": "9f2…", "email": "ada@…",
"displayName": "Ada" }]Admin panel
Not installed.
Nothing is served, nothing is bundled.
The only thing that changed is what a human can see.
A backend in about a minute
Scaffold a project, point it at a database, and read the OpenAPI spec it generates.
