Zum Inhalt springen

Backend-Übersicht

Das Rebase-Backend ist ein auf Hono basierender Node.js-Server, der Folgendes bereitstellt:

  • REST-API — Automatisch generierte CRUD-Endpunkte für jede Collection
  • Authentifizierung — JWT-Tokens, OAuth- und OIDC-Anmeldung, Magic Links, Einmalcodes, MFA, API-Schlüssel, Benutzer-/Rollenverwaltung
  • Storage — Datei-Upload/-Download über das lokale Dateisystem oder S3
  • WebSocket — Echtzeit-Datensynchronisierung über PostgreSQL LISTEN/NOTIFY
  • Entitätshistorie — Audit-Trail für jede Datenänderung
  • Database Branching — Sofortige, isolierte Datenbankkopien für Dev/Staging/Testing
  • Cron-Jobs — Geplante Hintergrundaufgaben mit Monitoring-Dashboard

Alles wird mit einer einzigen Funktion initialisiert:

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"
});

Seit 0.25 Dieser eine Aufruf ist eine Abkürzung über öffentliche Schritte: installUnhandledRejectionHandler verhindert, dass eine verirrte Rejection den Prozess beendet, createRebaseBackend baut das Backend, setDefaultBackend macht es zu dem, wofür die API auf Modulebene antwortet, und mount, attachRealtime und start liefern es aus. Um nur einige Routengruppen einzubinden, sie unter andere Pfade zu legen, die Timer einem anderen Prozess zu überlassen oder zwei Backends in einem Prozess zu betreiben, rufen Sie die Schritte selbst auf. Siehe Integration eines eigenen Servers.

Dieser Aufruf entspricht der ejected Form – derjenigen, die Sie nach rebase eject oder in einem Custom Server selbst schreiben. Ein per Scaffolding erstelltes Projekt hat ihn nicht: Die bereitgestellte Runtime bootet das Projekt, und jede Option wird als Umgebungsvariable in .env, als Export aus config/index.ts oder als Verzeichnis, das das Bundle in rebase.json deklariert, übergeben.

Beide Pfade führen zur selben RebaseBackendConfig. Dies ist die vollständige Übersicht.

Option Managed Runtime
basePath REBASE_BASE_PATH (Standard: /api)
collections, collectionsDir das von rebase.json deklarierte Verzeichnis config/collections/
functions, functionsDir backend/functions/
crons, cronsDir backend/crons/
bootstrappers, database DATABASE_URL, plus eine database("<key>")-Deklaration in config/resources.ts für jede Datenbank jenseits des Standards
auth JWT_SECRET, die OAUTH_*-Variablen und config/collections/users
storage die STORAGE_*-Variablen, plus eine bucket("<key>")-Deklaration in config/resources.ts für jeden Bucket jenseits des Standards
storageAuthorize export const storageAuthorize aus config/index.ts
storagePublicRead STORAGE_PUBLIC_READ
storageRenditionCache STORAGE_RENDITION_CACHE
storageInsecureAllowAnyAuthenticated STORAGE_ALLOW_ANY_AUTHENTICATED
callbacks export const callbacks aus config/index.ts
history REBASE_HISTORY (standardmäßig aktiviert) — nur die boolesche Form
enableSwagger REBASE_ENABLE_SWAGGER; nicht gesetzt bedeutet außerhalb der Produktion aktiviert
compression REBASE_COMPRESSION
maxBodySize REBASE_MAX_BODY_SIZE
logging LOG_LEVEL
provisionSchema, surfaces, ownership, functionsSelection, functionsUpstream REBASE_ROLE — siehe Split Processes
corsHandled CORS wird von der Runtime aus CORS_ORIGINS installiert
schemaVersion, runtimeVersion der Build stempelt beide in das Bundle ein
app, server, provisioningDriverResult die Runtime erstellt sie

Diese besitzen weder Umgebungsvariablen noch Konfigurationsexporte. Sie sind nur über einen manuell geschriebenen initializeRebaseBackend-Aufruf erreichbar – per rebase eject oder über einen Custom Server:

rateLimit · jobs · csrf · cronPersistence · functionsTimeoutMs · storagePolicies · storageTriggers · baas · liveSchema · rlsAudit · history in seiner Objektform ({ retention })

schemaEditor wird in einem erstellten Bundle zwangsweise deaktiviert: Der Editor schreibt Quelldateien von Collections um, und ein Bundle enthält kompilierten Output.

Nach der Initialisierung werden diese Routen gemountet:

Pfad Zweck
/api/auth/* Authentifizierung (Registrierung, Login, Refresh, OAuth, Magic Links, Einmalcodes, MFA)
/api/admin/* Benutzer, Rollen, Service-Schlüssel, Cron, Backups, Logs und Schema, jeweils hinter einem eigenen Scope
/api/storage/* Datei-Upload, -Download und -Löschung
/api/data/:slug CRUD-Operationen pro Collection (GET, POST, PATCH, DELETE)
/api/data/:slug/:id/history Änderungshistorie der Entität (wenn aktiviert)
/api/docs OpenAPI-Spezifikation (wenn enableSwagger: true)
/api/swagger Swagger UI (Dev-Modus, wenn enableSwagger: true)
/api/meta/contract Das Collection-Schema des Projekts (braucht schema:read)
/api/meta/schema-version Ein Versions-String für dieses Schema (nicht authentifiziert)
/api/functions/* Benutzerdefinierte Funktionsrouten (wenn functions oder functionsDir gesetzt ist)
/api/admin/cron/* Cron-Job-Verwaltung (cron:read / cron:write, wenn crons oder cronsDir gesetzt ist)
WebSocket beim Upgrade Echtzeit-Abonnements, auf / bei einer API unter /api

Wenn Sie initializeRebaseBackend() aufrufen, löst das Framework eine sequentielle, 5-stufige Boot-Sequenz aus:

[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]

Der Boot-Vorgang schlägt lautstark fehl. Wenn die Datenbank nicht erreichbar ist, die Anmeldedaten falsch sind oder das Collection-Schema nicht angewendet werden kann, wirft initializeRebaseBackend einen Fehler, es wird nichts ausgeliefert und der Prozess wird mit 1 beendet. Es gibt keinen herabgesetzten Modus und keinen Teil-Server: Ein Container, der seine Datenbank nicht erreichen kann, startet neu, und der Fehler, der ihn beendet hat, ist der letzte Eintrag in seinen Logs.

Das ist beabsichtigt. Ein Server, der hochfährt und Logins beantwortet, während jede /api/data/*-Route fehlschlägt, ist viel schwerer zu diagnostizieren als einer, der gar nicht erst startet – und ein Orchestrator kann auf einen Crash-Loop reagieren.

Vor der ersten Abfrage prüft der Boot-Vorgang die Verbindung und gibt die Diagnose aus: den Host und Port, die nicht erreicht werden konnten, die Begründung des Treibers selbst (ECONNREFUSED, password authentication failed for user "app") sowie die Lösung. Siehe Fehlerbehebung für die vollständige Liste aller Fehlerfälle.

Zwei Probes, die zwei unterschiedliche Fragen beantworten.

Pfad Greift auf die Datenbank zu Antwortet
/livez Nein 200 {"status":"ok"}, solange der Prozess läuft. Verwenden Sie dies für eine Liveness-Probe.
/health Ja, jede Datenquelle 200 {"status":"ok"}, wenn jede konfigurierte Datenquelle antwortet; 503 {"status":"degraded"}, wenn eine nicht antwortet. Verwenden Sie dies für eine Readiness-Probe.

Eine Liveness-Probe auf /health ist ein Fehler, der erwähnenswert ist: Ein kurzer Datenbank-Schluckauf würde den Orchestrator dazu veranlassen, einen ansonsten gesunden Prozess zu beenden, was einen kurzen Ausfall in eine Neustartschleife verwandeln würde.

Da /health nicht authentifiziert ist, gibt der Endpunkt das Ergebnis aus und nicht die Begründung – außerhalb der Entwicklungsumgebung nennt er lediglich, welche Datenquelle beeinträchtigt ist, und sonst nichts. Der Fehlertext des Treibers nennt Host, Port, Datenbankname und Rolle, und dies wird in die Logs geschrieben. Beide Pfade werden auch unter basePath bereitgestellt (/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" };
}

Fünf davon werden leicht übersehen und verändern Verhaltensweisen, die Sie sonst nur beobachten können:

Schlüssel Standard Funktion
compression true gzip/deflate für API-Antworten, ausgehandelt über Accept-Encoding. Bereits komprimierte, gestreamte und mit no-transform versehene Payloads bleiben unberührt, ebenso Payloads unter 1 KB (gzip kostet dort mehr, als es einspart), sodass es sicher aktiviert bleiben kann – eine große JSON-Liste schrumpft typischerweise um das ~20-Fache. Setzen Sie dies auf false, wenn nginx, Cloudflare oder ein anderer vorgeschalteter Proxy bereits komprimiert, um doppelte Verarbeitung zu vermeiden. Umgebungsvariable: REBASE_COMPRESSION.
maxBodySize 10485760 (10MB) Obergrenze für Request-Bodies bei API-Routen; 0 deaktiviert sie. Storage-Uploads verwenden das separate maxFileSize (50MB) der Storage-Konfiguration, welches für diese Routen Vorrang hat. Umgebungsvariable: REBASE_MAX_BODY_SIZE.
csrf deaktiviert Opt-in. Eine BaaS-API wird von mobilen Apps, SPAs auf anderen Domains und CLI-Tools aufgerufen, von denen keines einen Origin-Header sendet, den eine feste Liste akzeptieren würde – daher ist dies standardmäßig nicht aktiviert. Aktivieren Sie es mit den Origins, die Ihre Browser-Clients verwenden. Keine Umgebungsvariable vorhanden: Führen Sie rebase eject aus, um dies zu konfigurieren.
cronPersistence true Gibt an, ob Ausführungsprotokolle in rebase.cron_logs geschrieben werden. Bei false laufen die Jobs weiter und der Verlauf verbleibt nur im Speicher, was das Studio-Panel bei einem Neustart dann verliert.
schemaEditor außerhalb der Produktion aktiv, wenn collectionsDir gesetzt ist Schaltet die Schema-Editor-Routen explizit ein oder aus. Der Editor schreibt Quelldateien von Collections um, benötigt also ein Verzeichnis, in das er schreiben kann – und ein erstelltes Bundle hat keines, weshalb ein Deployment diese Routen niemals besitzt.

Der Rest von RebaseBackendConfig ist entweder auf einer eigenen Seite dokumentiert (auth, storage, jobs, callbacks, liveSchema, rlsAudit) oder als @internal markiert: bootstrappers, provisioningDriverResult, provisionSchema, corsHandled, functionsSelection, functionsUpstream und runtimeVersion werden von bootFromBundle aus der Umgebung befüllt, und deren manuelle Übergabe führt zu Konflikten mit der Runtime darüber, was dieser Prozess eigentlich darstellt.

initializeRebaseBackend gibt das Backend zurück, das es gebaut hat, bereits eingebunden und gestartet. Sein Typ ist RebaseBackendInstance, und das ist RebaseBackend, der Typ, den createRebaseBackend zurückgibt:

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

Seit 0.25 Es trägt außerdem rebase, den In-Process-Client, fetch(request), routes, die Routengruppen als Hono-Sub-Apps, services, seine eigenen JWT-Schlüssel, sein Zugriffsmodell, seinen API-Schlüssel-Speicher und seine Rate-Limiter, sowie verifyCredential(token); siehe Integration eines eigenen Servers.

Hinweis: Obwohl die instance diese internen Dienste verfügbar macht, sollte Anwendungscode (wie benutzerdefinierte Funktionen und Cron-Jobs) das rebase-Singleton aus @rebasepro/server verwenden, um mit der Backend-API zu interagieren. Seit 0.25 Innerhalb eines Requests, Cron-Laufs oder Jobs ist rebase das Backend, das ihn bedient; überall sonst ist es der Prozess-Standard, den initializeRebaseBackend auf sein Backend setzt.

Die REST-API wird automatisch aus Ihren Collections generiert. Jede Collection erhält diese Endpunkte:

Methode Pfad Beschreibung
GET /api/data/:slug Entitäten auflisten – Filtern, Sortieren, Paging und Suche erfolgen über Query-Parameter
GET /api/data/:slug/count Wie viele Zeilen auf dieselbe Abfrage zutreffen
GET /api/data/:slug/aggregate count/sum/avg/min/max, optional gruppiert
GET /api/data/:slug/:id Eine einzelne Entität abrufen
POST /api/data/:slug Eine neue Entität erstellen
PATCH /api/data/:slug/:id Die übergebenen Felder aktualisieren
DELETE /api/data/:slug/:id Einen Datensatz löschen
POST /api/data/:slug/bulk Mehrere Zeilen in einer einzigen Transaktion erstellen
PATCH /api/data/:slug/bulk Mehrere Zeilen in einer einzigen Transaktion aktualisieren
POST /api/data/:slug/bulk/delete Mehrere Zeilen in einer einzigen Transaktion löschen

Es gibt eine Referenz dafür, und es ist nicht diese Seite. Die Seite REST-API dokumentiert beide Abfragedialekte, die der Server akzeptiert – das Format im PostgREST-Stil ?column=op.value und das JSON-Format ?where= – zusammen mit orderBy, limit/offset, include, fields, searchString und der Vektorsuche. Endpunkte ist der Index aller vom Server gemounteten Routen, einschließlich der generierten.

Ein Parameter, den der Server nicht reserviert hat, wird als Filter auf die Spalte dieses Namens interpretiert. Ein erfundener Parameter schlägt daher nicht fehl: Er stimmt stillschweigend mit nichts überein.

Der WebSocket-Server dockt an denselben HTTP-Server an und bietet Echtzeit-Abonnements. Seit 0.25 Er beantwortet Upgrades auf genau einem Pfad, / bei einer API unter /api, wo sich das SDK einwählt, und überlässt jeden anderen Pfad Ihren eigenen upgrade-Listenern; unter 0.24 nimmt er jedes Upgrade auf dem Server an:

  • Collection-Änderungen abonnieren — Benachrichtigungen erhalten, wenn eine beliebige Entität in einer Collection erstellt, aktualisiert oder gelöscht wird
  • Entitätsänderungen abonnieren — Benachrichtigungen erhalten, wenn sich eine bestimmte Entität ändert
  • Automatisches Reconnection-Handling im typisierten SDK

Das Backend verwendet intern PostgreSQL LISTEN/NOTIFY. Geben Sie für Multi-Instanz-Deployments einen connectionString in Ihrem PostgresBootstrapper an, um instanzübergreifendes Broadcasting zu aktivieren.

Jeder Fehler – von jeder Route, in jedem Subsystem – wird in einem einheitlichen Envelope zurückgegeben:

{
"error": {
"message": "Entity not found",
"code": "NOT_FOUND",
"requestId": "9f1c0b8e-4d2a-4e1b-9d0f-2c7a5b3e6a11"
}
}
Feld Immer vorhanden Bedeutung
message ja Geschrieben für Personen, die eine Konsole lesen. Nennt das Problem, nicht die interne Regel.
code ja SCREAMING_SNAKE_CASE und stabil. Dies ist das Feld für Verzweigungen.
details nein Strukturierte Nutzdaten, wenn sich die Ablehnung auf etwas Konkretes bezieht – eine Liste fehlerhafter Pfade, ein Satz unbekannter Felder.
requestId nein Vorhanden, wenn die Anfrage eine ID trug oder ihr eine zugewiesen wurde; spiegelt X-Request-ID wider. Geben Sie diese in einem Bug-Report an.

Der HTTP-Status befindet sich auf der Response, nicht im Body. Verzweigen Sie basierend auf code, nicht auf message – Meldungen sind für Menschen geschrieben und können sich ändern.

Das typisierte SDK wandelt jeden dieser Fehler in einen RebaseApiError um, der status, code und details enthält – einschließlich der Fehler, die den Server gar nicht erst erreicht haben. Eine abgewiesene Verbindung, ein DNS-Fehler, CORS oder ein Verbindungsabbruch kommen als status: 0, code: "NETWORK_ERROR" an, wobei der Fehler der Runtime selbst unter cause liegt, anstatt als beliebiger Fehler, mit dem fetch gerade abbrechen wollte. Eine Antwort ohne Envelope — das 413 eines Proxys, das 502 eines Load Balancers — erhält dennoch einen code aus ihrem Status (PAYLOAD_TOO_LARGE, SERVICE_UNAVAILABLE, INTERNAL_ERROR, …), und ein außerhalb eines Browsers erstellter Client ohne baseUrl scheitert mit BASE_URL_REQUIRED statt mit einem Netzwerkfehler. Seit 0.25 Ein Lesezugriff, der einen Erfolg ohne die erwartete Form erhält — ein 204, ein leerer Body, eine Seite ohne data-Liste —, scheitert mit UNEXPECTED_RESPONSE, statt als „keine Zeilen” aufgelöst zu werden, und createMany, updateMany, deleteMany oder batch scheitern mit INVALID_BULK_BODY, dem Code, den der Server für denselben Fehler verwendet, wenn etwas anderes als ein Array übergeben wird. Daher fängt Anwendungscode eine einzige Klasse ab:

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;
}
}