Ir al contenido

Descripción general del backend

El backend de Rebase es un servidor Node.js construido sobre Hono que proporciona:

  • API REST — Endpoints CRUD autogenerados para cada colección
  • Autenticación — Tokens JWT, inicio de sesión mediante OAuth y OIDC, enlaces mágicos (magic links), códigos de un solo uso, MFA, claves de API y gestión de usuarios/roles
  • Almacenamiento — Carga/descarga de archivos con sistema de archivos local o S3
  • WebSocket — Sincronización de datos en tiempo real mediante PostgreSQL LISTEN/NOTIFY
  • Historial de entidades — Pista de auditoría para cada cambio de datos
  • Bifurcación de base de datos (Database Branching) — Copias de bases de datos instantáneas y aisladas para desarrollo/staging/pruebas
  • Tareas programadas (Cron Jobs) — Tareas en segundo plano programadas con panel de monitorización

Todo se inicializa con una sola función:

import { initializeRebaseBackend } from "@rebasepro/server";
import { createPostgresAdapter } from "@rebasepro/server-postgres";
import { env } from "./env";
const instance = await initializeRebaseBackend({
app,
server,
collectionsDir: "./config/collections",
database: createPostgresAdapter({
connection: db,
schema: { tables, enums, relations }
}),
auth: {
jwtSecret: env.JWT_SECRET,
},
storage: { type: "local", basePath: "./uploads" },
history: true,
enableSwagger: env.NODE_ENV !== "production"
});

Desde 0.25 Esa única llamada es un atajo sobre pasos públicos: installUnhandledRejectionHandler evita que un rechazo perdido termine el proceso, createRebaseBackend construye el backend, setDefaultBackend lo convierte en aquel por el que responde la API a nivel de módulo, y mount, attachRealtime y start lo sirven. Para montar solo algunos grupos de rutas, colocarlos en otras rutas, dejar los temporizadores a otro proceso o ejecutar dos backends en un mismo proceso, llame usted mismo a los pasos. Consulte Integración de servidor personalizado.

Esa llamada es la forma expulsada (ejected): la que usted mismo escribe después de rebase eject, o en un servidor personalizado. Un proyecto generado mediante scaffolding no la tiene: el runtime publicado arranca el proyecto y cada opción llega como una variable de entorno en .env, una exportación desde config/index.ts o un directorio que el bundle declara en rebase.json.

Ambas vías llegan al mismo RebaseBackendConfig. Este es el mapa completo.

Opción Runtime gestionado
basePath REBASE_BASE_PATH (por defecto /api)
collections, collectionsDir el directorio config/collections/, declarado por rebase.json
functions, functionsDir backend/functions/
crons, cronsDir backend/crons/
bootstrappers, database DATABASE_URL, más una declaración database("<key>") en config/resources.ts para cada base de datos además de la predeterminada
auth JWT_SECRET, las variables OAUTH_* y config/collections/users
storage las variables STORAGE_*, más una declaración bucket("<key>") en config/resources.ts para cada bucket además del predeterminado
storageAuthorize export const storageAuthorize desde config/index.ts
storagePublicRead STORAGE_PUBLIC_READ
storageRenditionCache STORAGE_RENDITION_CACHE
storageInsecureAllowAnyAuthenticated STORAGE_ALLOW_ANY_AUTHENTICATED
callbacks export const callbacks desde config/index.ts
history REBASE_HISTORY (activado por defecto) — solo en su forma booleana
enableSwagger REBASE_ENABLE_SWAGGER; sin definir significa activado fuera de producción
compression REBASE_COMPRESSION
maxBodySize REBASE_MAX_BODY_SIZE
logging LOG_LEVEL
provisionSchema, surfaces, ownership, functionsSelection, functionsUpstream REBASE_ROLE — consulte Procesos divididos
corsHandled CORS es instalado por el runtime a partir de CORS_ORIGINS
schemaVersion, runtimeVersion la compilación estampa ambos en el bundle
app, server, provisioningDriverResult el runtime los crea

Estas no tienen variable de entorno ni exportación de configuración. Solo son accesibles desde una llamada manual a initializeRebaseBackend: rebase eject o un servidor personalizado:

rateLimit · jobs · csrf · cronPersistence · functionsTimeoutMs · storagePolicies · storageTriggers · baas · liveSchema · rlsAudit · history en su forma de objeto ({ retention })

schemaEditor se desactiva de forma forzada en un bundle compilado: el editor reescribe los archivos fuente de las colecciones, y un bundle contiene la salida compilada.

Tras la inicialización, se montan estas rutas:

Ruta Propósito
/api/auth/* Autenticación (registro, inicio de sesión, refresco, OAuth, magic links, códigos de un solo uso, MFA)
/api/admin/* Usuarios, roles, claves de servicio, cron, copias de seguridad, registros y esquema, cada uno tras su propio alcance
/api/storage/* Carga, descarga y eliminación de archivos
/api/data/:slug Operaciones CRUD por colección (GET, POST, PATCH, DELETE)
/api/data/:slug/:id/history Historial de cambios de entidades (cuando está habilitado)
/api/docs Especificación OpenAPI (cuando enableSwagger: true)
/api/swagger Swagger UI (modo desarrollo, cuando enableSwagger: true)
/api/meta/contract El esquema de colecciones del proyecto (necesita schema:read)
/api/meta/schema-version Una cadena de versión para dicho esquema (sin autenticación)
/api/functions/* Rutas de funciones personalizadas (cuando functions o functionsDir está configurado)
/api/admin/cron/* Gestión de tareas cron (cron:read / cron:write, cuando crons o cronsDir está configurado)
WebSocket en upgrade Suscripciones en tiempo real, en / para una API en /api

Cuando invoca initializeRebaseBackend(), el framework desencadena una secuencia de arranque secuencial de 5 fases:

[Start Boot]
│
▼
1. ENV validation (Zod parsing of jwt, databases, cors)
│
▼
2. Dynamic Collection Loading (Chokidar watches .ts files, AST parsing)
│
▼
3. Database Bootstrapping (Acquires advisory lock, creates schemas/auth/helper SQL functions)
│
▼
4. Service Initialization (Auth, Storage S3/Local client instances, Cron store seeding)
│
▼
5. Route Mounting & Edge Loading (Hono controllers, custom functions, WebSocket binding)
│
▼
[Boot Complete]

El arranque falla ruidosamente. Si la base de datos es inaccesible, las credenciales son incorrectas o no se puede aplicar el esquema de la colección, initializeRebaseBackend lanza un error, no se sirve nada y el proceso termina con código 1. No existe un modo degradado ni un servidor parcial: un contenedor que no puede comunicarse con su base de datos se reinicia, y el error que provocó su caída es lo último que aparece en sus registros.

Esto es deliberado. Un servidor que se inicia respondiendo al inicio de sesión mientras todas las rutas /api/data/* fallan es mucho más difícil de diagnosticar que uno que nunca llega a levantarse, y un orquestador puede actuar ante un bucle de reinicios (crash loop).

Antes de la primera consulta, el arranque sondea la conexión e imprime el diagnóstico: el host y puerto a los que no pudo acceder, el motivo propio del controlador (ECONNREFUSED, password authentication failed for user "app") y la solución. Consulte Resolución de problemas para ver la lista fallo por fallo.

Dos sondeos que responden a dos preguntas diferentes.

Ruta Toca la base de datos Responde
/livez No 200 {"status":"ok"} mientras el proceso está en ejecución. Utilícelo para un sondeo de liveness (supervivencia).
/health Sí, todas las fuentes de datos 200 {"status":"ok"} cuando todas las fuentes de datos configuradas responden; 503 {"status":"degraded"} cuando alguna no lo hace. Utilícelo para un sondeo de readiness (disponibilidad).

Configurar un sondeo de liveness en /health es un error que vale la pena mencionar: una pequeña interrupción en la base de datos haría que el orquestador termine un proceso que por lo demás es saludable, convirtiendo una breve caída en un bucle de reinicios.

/health no requiere autenticación, por lo que publica el veredicto y no el motivo; fuera de desarrollo, solo indica qué fuente de datos está degradada y nada más. El texto de error del controlador cita el host, el puerto, el nombre de la base de datos y el rol, y eso va a los registros. Ambas rutas también se sirven bajo basePath (/api/health).


interface RebaseBackendConfig {
// HTTP framework
app: Hono; // Hono application instance
server: Server; // Node.js HTTP server (for WebSocket attachment)
basePath?: string; // Route prefix (default: "/api")
// Collections
collections?: CollectionConfig[]; // Your collection definitions
collectionsDir?: string; // Auto-load collections from a directory
// Database adapter (PostgreSQL, SQLite, etc.)
database?: DatabaseAdapter;
// Authentication configuration or custom adapter
auth?: RebaseAuthConfig | AuthAdapter;
// File storage
storage?: BackendStorageConfig | Record<string, BackendStorageConfig>;
// Entity history
history?: boolean | HistoryConfig;
// OpenAPI/Swagger
enableSwagger?: boolean;
// Custom API endpoints: values, or a directory of files (not both)
functions?: Record<string, FunctionApp>; // { "send-invoice": app } → /api/functions/send-invoice
functionsDir?: string; // Auto-load Hono routes from a directory
// Scheduled tasks: values, or a directory of files (not both)
crons?: Record<string, CronJobDefinition>; // { "nightly-report": defineCron(…) }
cronsDir?: string; // Auto-load cron jobs from a directory
cronPersistence?: boolean; // Write run logs to rebase.cron_logs (default: true)
// HTTP behaviour
compression?: boolean; // gzip/deflate for API responses (default: true)
maxBodySize?: number; // Request-body ceiling in bytes (default: 10MB; 0 disables)
csrf?: { origin: string | string[] | ((origin: string) => boolean) };
// Schema editing
schemaEditor?: boolean; // Force the schema-editor routes on or off
// Logging
logging?: { level?: "error" | "warn" | "info" | "debug" };
}

Cinco de ellas son fáciles de pasar por alto y modifican comportamientos que de otro modo solo podría observar:

Clave Por defecto Qué hace
compression true gzip/deflate en las respuestas de la API, negociado a partir de Accept-Encoding. Los cuerpos ya comprimidos, transmitidos por streaming o marcados como no-transform no se tocan, y tampoco los de menos de 1 KB (ahí gzip cuesta más de lo que ahorra), por lo que es seguro dejarlo activado; una lista JSON grande normalmente se reduce ~20 veces. Establézcalo en false cuando nginx, Cloudflare u otro proxy intermedio ya aplique compresión, para evitar un doble procesamiento. Entorno: REBASE_COMPRESSION.
maxBodySize 10485760 (10MB) Límite máximo para los cuerpos de las solicitudes en rutas de API; 0 lo desactiva. Las cargas de almacenamiento usan el maxFileSize propio de la configuración de almacenamiento (50MB), el cual prevalece en esas rutas. Entorno: REBASE_MAX_BODY_SIZE.
csrf desactivado Opcional (Opt-in). A una API BaaS la llaman aplicaciones móviles, SPAs en otros dominios y herramientas de CLI, ninguno de los cuales envía un Origin que una lista fija aceptaría, por lo que no está activado por defecto. Actívelo con los orígenes que utilizan sus clientes de navegador. No existe variable de entorno: haga eject para configurarlo.
cronPersistence true Indica si los registros de ejecución llegan a rebase.cron_logs. false mantiene los trabajos en ejecución y el historial únicamente en memoria, el cual el panel de Studio pierde al reiniciar.
schemaEditor activado fuera de producción, cuando collectionsDir está configurado Fuerza la activación o desactivación de las rutas del editor de esquemas. El editor reescribe los archivos fuente de las colecciones, por lo que necesita un directorio donde escribir, y un bundle compilado no tiene ninguno, razón por la cual un despliegue nunca lo incluye.

El resto de RebaseBackendConfig está documentado en su propia página (auth, storage, jobs, callbacks, liveSchema, rlsAudit) o marcado como @internal: bootFromBundle completa bootstrappers, provisioningDriverResult, provisionSchema, corsHandled, functionsSelection, functionsUpstream y runtimeVersion a partir del entorno, y pasarlos manualmente es una forma de entrar en conflicto con el runtime sobre qué es este proceso.

initializeRebaseBackend devuelve el backend que construyó, ya montado y en marcha. Su tipo es RebaseBackendInstance, que es RebaseBackend, el tipo que devuelve createRebaseBackend:

const instance = await initializeRebaseBackend(config);
// Internal service access
instance.driver // Default data driver
instance.driverRegistry // All drivers (for multi-database)
instance.realtimeService // Default realtime service
instance.auth?.userService // User management
instance.storageController // Default storage
instance.storageRegistry // All storage backends
instance.collectionRegistry // Collection metadata
instance.history?.historyService // Entity history
instance.cronScheduler // Cron job scheduler (when crons or cronsDir is set)
instance.jobQueue // Durable job queue (when jobs are enabled)
instance.setCollectionCallbacks(slug, callbacks) // Replace a collection's callbacks
instance.shutdown() // Drain and stop; also closes the server it was given

Desde 0.25 También incluye rebase, el cliente en el proceso; fetch(request); routes, los grupos de rutas como sub-apps de Hono; services, sus propias claves JWT, su modelo de acceso, su almacén de claves de API y sus limitadores de tasa; y verifyCredential(token). Consulte Integración de servidor personalizado.

Nota: Aunque la instance expone estos servicios internos, el código de la aplicación (como funciones personalizadas y tareas cron) debe usar el singleton rebase de @rebasepro/server para interactuar con la API del backend. Desde 0.25 Dentro de una solicitud, una ejecución de cron o un trabajo, rebase es el backend que lo está sirviendo; en cualquier otro lugar es el valor por defecto del proceso, que initializeRebaseBackend establece como su propio backend.

La API REST se genera automáticamente a partir de sus colecciones. Cada colección obtiene estos endpoints:

Método Ruta Descripción
GET /api/data/:slug Listar entidades: el filtrado, la ordenación, la paginación y la búsqueda son parámetros de consulta
GET /api/data/:slug/count Cuántas filas coinciden con la misma consulta
GET /api/data/:slug/aggregate count/sum/avg/min/max, opcionalmente agrupados
GET /api/data/:slug/:id Obtener una sola entidad
POST /api/data/:slug Crear una nueva entidad
PATCH /api/data/:slug/:id Actualizar los campos enviados
DELETE /api/data/:slug/:id Eliminar un registro
POST /api/data/:slug/bulk Crear múltiples filas en una sola transacción
PATCH /api/data/:slug/bulk Actualizar múltiples filas en una sola transacción
POST /api/data/:slug/bulk/delete Eliminar múltiples filas en una sola transacción

Existe una referencia específica para ellos y no es esta página. API REST documenta ambos dialectos de consulta que acepta el servidor —el formato estilo PostgREST ?column=op.value y el formato JSON ?where=— junto con orderBy, limit/offset, include, fields, searchString y búsqueda vectorial. Endpoints es el índice de todas las rutas que monta el servidor, incluidas las generadas.

Un parámetro que el servidor no reserve se interpreta como un filtro en la columna con ese nombre, por lo que uno inventado no falla: simplemente no coincide con nada de forma silenciosa.

El servidor WebSocket se adjunta al mismo servidor HTTP y proporciona suscripciones en tiempo real. Desde 0.25 Responde a los upgrades en una sola ruta, / para una API en /api, que es donde se conecta el SDK, y deja cualquier otra ruta a sus propios listeners de upgrade; en la 0.24 acepta todos los upgrades del servidor:

  • Suscribirse a cambios en colecciones: reciba notificaciones cuando cualquier entidad de una colección se cree, actualice o elimine
  • Suscribirse a cambios en entidades: reciba notificaciones cuando una entidad específica cambie
  • Gestión automática de reconexión en el SDK tipado

El backend utiliza PostgreSQL LISTEN/NOTIFY internamente. Para despliegues con múltiples instancias, proporcione un connectionString en su PostgresBootstrapper para habilitar la difusión entre instancias.

Cada fallo —desde cualquier ruta, en cualquier subsistema— se devuelve en una única estructura:

{
"error": {
"message": "Entity not found",
"code": "NOT_FOUND",
"requestId": "9f1c0b8e-4d2a-4e1b-9d0f-2c7a5b3e6a11"
}
}
Campo Siempre presente Qué es
message sí Escrito para una persona que lee una consola. Nombra el obstáculo, no la regla.
code sí SCREAMING_SNAKE_CASE y estable. Este es el campo sobre el que bifurcar la lógica condicional.
details no Carga útil (payload) estructurada cuando el rechazo trata sobre algo concreto: una lista de rutas fallidas, un conjunto de campos desconocidos.
requestId no Presente cuando la solicitud incluía o se le asignó uno; refleja X-Request-ID. Cítelo en un reporte de error.

El estado HTTP se encuentra en la respuesta, no en el cuerpo. Bifurque sobre code, no sobre message; los mensajes están escritos para humanos y pueden cambiar con libertad.

El SDK tipado convierte cada uno de estos en un RebaseApiError que incluye status, code y details, incluidos los fallos que ni siquiera llegaron a un servidor. Una conexión rechazada, un fallo de DNS, CORS o una cancelación (abort) llegan como status: 0, code: "NETWORK_ERROR", con el propio error del runtime en cause, en lugar de lo que a fetch le haya parecido rechazar. Una respuesta sin envoltorio (envelope) — un 413 de un proxy, un 502 de un balanceador de carga — recibe igualmente un code a partir de su estado (PAYLOAD_TOO_LARGE, SERVICE_UNAVAILABLE, INTERNAL_ERROR, …), y un cliente construido fuera de un navegador sin baseUrl falla con BASE_URL_REQUIRED en lugar de con un error de red. Desde 0.25 Una lectura que recibe un éxito sin la forma que lee — un 204, un cuerpo vacío, una página sin lista data — falla con UNEXPECTED_RESPONSE en lugar de resolverse como «sin filas», y createMany, updateMany, deleteMany o batch, al recibir algo que no es un array, fallan con INVALID_BULK_BODY, el código que usa el servidor para el mismo error. Por lo tanto, el código de la aplicación captura una sola clase:

async function setPrice(id: string, price: number) {
try {
return await client.data.products.update(id, { price });
} catch (e) {
if (e instanceof RebaseApiError && e.code === "NOT_FOUND") return null;
throw e;
}
}