Hono · Drizzle · PostgreSQL · OpenAPI

El backend,
por sí solo.

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, sin escribirlo

Cada tabla que expones se convierte en 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 superficie generada
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

El cliente tipado

Autocompletado que sale de tu esquema

rebase generate-sdk convierte tus colecciones en un tipo Database. Pásalo al cliente y los nombres de colección, los nombres de campo, los operadores de filtro y las formas de retorno se comprueban en tiempo de compilación — incluidos los campos que pediste en include.

Renombra una columna en la colección y los puntos de llamada se ponen rojos antes de que se despliegue nada. El repositorio que consume la API no tiene por qué ser el que la define: --from <url> genera el mismo cliente desde un backend en marcha, en un proyecto sin esquema propio. Tu web, tu panel y tu cliente móvil se quedan cada uno en su repositorio, y CI detecta un cliente desfasado antes que un usuario.

El recorrido completo por el 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

Los cambios llegan, se hayan hecho como se hayan hecho

listen() en cualquier colección y cada inserción, actualización y borrado llega al cliente por WebSocket. Las suscripciones respetan la misma seguridad a nivel de fila que una lectura, así que a nadie se le envía una fila que no habría podido pedir.

Las escrituras hechas fuera de la API también cuentan: una fila cambiada desde psql, un cron o el editor SQL de Studio emite el mismo evento, porque la captura de cambios ocurre en la base de datos. Los canales de difusión y la presencia son por instancia por defecto; pon realtime.bus a { type: "postgres" } y se reparten entre réplicas por LISTEN/NOTIFY, o dale un transporte propio.

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

Las reglas viven en Postgres, no en este servidor

Las peticiones se ejecutan como un rol restringido de la base de datos. Tus securityRules compilan a políticas reales de seguridad a nivel de fila, así que una consulta que no debería devolver nada no devuelve nada — venga por el SDK, por REST, por el panel de administración, por la clave de API de un agente o por una sesión de psql. Una colección sin política no sirve ninguna fila: el valor por defecto es cerrado, no abierto.

config/collections/orders.tslo que escribes
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 — lo que aplica la base de datosgenerated
-- 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.
El modelo de seguridad al completo·¿Ya estás en Postgres? npx @rebasepro/rls-check lee DATABASE_URL y lo audita sin instalar nada.

Un servidor

Todo montado, nada que ensamblar

Esta es la tabla de rutas de un proyecto por defecto — un proceso, un despliegue, ninguna malla de servicios que dibujar. Cada una de estas existe porque construirla tú es una semana que preferirías gastar en otra cosa.

Un proceso es el valor por defecto, no el techo. El mismo build también arranca como API, capa de funciones y worker — una variable por proceso decide qué rutas se montan, qué temporizadores disparan y cuál es el dueño del esquema.

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

Tecnología aburrida, a propósito

Una aplicación Hono y un esquema de Drizzle. Dos cosas que tu equipo puede leer, depurar y extender sin aprender un framework — y sin esperar a que nosotros expongamos 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
  • Móntalo como sub-app en un servidor que ya tienes
  • Tu middleware, tus rutas, tu manejo de errores
  • Node, Docker, Railway, Fly.io o bare metal
relaciones, resueltas en una consultaDrizzle
// 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
  • Uno a uno, uno a muchos y muchos a muchos
  • Selección de campos — trae las columnas que pediste
  • Paginación por cursor y por offset

La otra mitad

Headless por defecto

El panel de administración es un cliente de esta API, no una capa dentro de ella. Lee a través de las mismas políticas que tu aplicación. Actívalo abajo: el proyecto gana una dependencia y un bloque admin anidado, aparece un panel de administración — y la respuesta de la API de la derecha no se mueve.

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.

Apúntalo a tu base de datos.

Genera contra una base de datos Postgres que ya ejecutas y lee las rutas REST, el documento OpenAPI y las políticas que generó.

~pnpm dlx @rebasepro/cli init