Hono · Drizzle · PostgreSQL · OpenAPI

Le backend,
à lui seul.

REST, a typed SDK, realtime over WebSocket, auth, storage and an OpenAPI spec — with every access rule enforced by the database itself. Point it at your own Postgres, or run rebase dev and get one without installing anything. No admin panel required.

rebase-introspect

Connect to your Postgres database

Point Rebase at your connection string. Everything runs on your infrastructure.

DATABASE_URL
Status: Connected
v3.1.2

REST, sans l'écrire

Chaque table que vous exposez devient un 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.

la surface générée
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/42

Le client typé

De l'autocomplétion qui vient de votre schéma

rebase generate-sdk transforme vos collections en un type Database. Passez-le au client et les noms de collection, les noms de champ, les opérateurs de filtre et les formes de retour sont tous vérifiés à la compilation — y compris les champs demandés dans include.

Renommez une colonne dans la collection et les points d'appel passent au rouge avant toute livraison. Le dépôt qui consomme l'API n'a pas besoin d'être celui qui la définit : --from <url> génère le même client depuis un backend en cours d'exécution, dans un projet sans schéma propre. Votre application web, votre panneau et votre client mobile restent chacun dans leur dépôt, et la CI repère un client périmé avant un utilisateur.

La visite complète du SDK
src/orders.ts
// 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,
  });

Realtime

Les changements arrivent, quelle qu'en soit l'origine

listen() sur n'importe quelle collection et chaque insertion, mise à jour et suppression arrive au client par WebSocket. Les abonnements respectent la même sécurité au niveau des lignes qu'une lecture : on ne pousse jamais à un abonné une ligne qu'il n'aurait pas pu récupérer.

Les écritures faites hors de l'API comptent aussi — une ligne modifiée depuis psql, un cron ou l'éditeur SQL de Studio émet le même événement, car la capture de changement se fait dans la base. Les canaux de diffusion et la présence sont par instance par défaut ; réglez realtime.bus sur { type: "postgres" } et ils se propagent entre répliques via LISTEN/NOTIFY, ou fournissez votre propre transport.

orders · four writers, one streamLISTEN/NOTIFY
  • RESTPATCH /api/data/orders/42your app
  • psqlUPDATE orders SET status = 'shipped' WHERE id = 42;a person at a terminal
  • cronUPDATE orders SET status = 'closed' WHERE …a scheduled job
  • StudioUPDATE orders … — run from the SQL editorthe admin panel

→ one change event: orders · UPDATE · id 42

→ each subscriber re-reads the row under its own access rules

Four ways to write to the orders table — the REST API, psql, a cron job and Studio's SQL editor — all produce the same change event, which each subscriber receives filtered by its own access rules.

Authorization

Les règles vivent dans Postgres, pas dans ce serveur

Les requêtes s'exécutent sous un rôle de base restreint. Vos securityRules se compilent en véritables politiques de sécurité au niveau des lignes : une requête qui ne devrait rien renvoyer ne renvoie rien — qu'elle arrive par le SDK, par REST, par le panneau d'administration, par la clé d'API d'un agent ou par une session psql. Une collection sans politique ne sert aucune ligne : par défaut c'est fermé, pas ouvert.

config/collections/orders.tsce que vous écrivez
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"] },
  ],
};
psql — ce que la base appliquegenerated
-- 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.
Le modèle de sécurité en entier·Déjà sur Postgres ? npx @rebasepro/rls-check lit DATABASE_URL et l'audite sans rien installer.

Un serveur

Tout est monté, rien à assembler

Voici la table de routage d'un projet par défaut — un processus, un déploiement, aucun maillage de services à dessiner. Chacune existe parce que la construire soi-même coûte une semaine que vous préféreriez passer ailleurs.

Un processus est la valeur par défaut, pas le plafond. Le même build démarre aussi en API, en étage de fonctions et en worker — une variable par processus décide quelles routes sont montées, quels minuteurs se déclenchent et lequel possède le schéma.

Topics and jobs. Publish an event, subscribe by name. One durable row per subscriber, each retrying on its own schedule — and a publish inside a transaction that rolls back never happened.

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 — schema, rows and policies — with one command to switch your project onto one and back.

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.

rebase dev — mounted routes
  • /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 TCP
  • WebSocketrow subscriptions, broadcast channels and presence on the same server

Underneath

De la technologie ennuyeuse, exprès

Une application Hono et un schéma Drizzle. Deux choses que votre équipe peut lire, déboguer et étendre sans apprendre un framework — et sans attendre que nous exposions un hook.

backend/src/index.tsHono
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
  • Montez-le comme sous-app sur un serveur que vous exploitez déjà
  • Vos middlewares, vos routes, votre gestion d'erreurs
  • Node, Docker, Railway, Fly.io ou bare metal
relations, résolues en une requêteDrizzle
// 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
  • Un-à-un, un-à-plusieurs et plusieurs-à-plusieurs
  • Sélection de champs — récupérez les colonnes demandées
  • Pagination par curseur et par offset

L'autre moitié

Headless par défaut

Le panneau d'administration est un client de cette API, pas une couche interne. Il lit à travers les mêmes politiques que votre application. Activez-le ci-dessous : le projet gagne une dépendance et un bloc admin imbriqué, un panneau d'administration apparaît — et la réponse d'API à droite ne bouge pas.

A headless project: no admin block, no panel — just the API.

Your project

config/
collections/users.ts

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
GET /api/data/users
200 OK · application/json
[{ "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.

Pointez-le vers votre base de données.

Échafaudez sur une base Postgres que vous exploitez déjà et lisez les routes REST, le document OpenAPI et les politiques générées.

~pnpm dlx @rebasepro/cli init