Skip to content

Project Structure

A Rebase project generated by npx create-rebase-app has three interconnected packages:

my-app/
├── .env # Environment variables (DATABASE_URL, JWT_SECRET, etc.)
├── package.json # Root workspace config
├── frontend/ # React admin panel (Vite)
│ ├── src/
│ │ ├── App.tsx # Main application component
│ │ ├── main.tsx # React entry point
│ │ └── index.css # Global styles
│ ├── package.json
│ └── vite.config.ts
├── backend/ # Node.js API server (Hono)
│ ├── src/
│ │ ├── index.ts # Server entry — initializes Rebase backend
│ │ └── schema.generated.ts # Auto-generated Drizzle schema
│ ├── drizzle.config.ts # Drizzle ORM configuration
│ ├── Dockerfile
│ └── package.json
└── shared/ # Collection definitions
└── collections/
├── index.ts # Exports all collections
└── products.ts # Example: products collection

The frontend is a standard Vite + React + TypeScript application. The key file is App.tsx, which wires together all Rebase controllers:

import { Rebase, Scaffold, AppBar, Drawer, ... } from "@rebasepro/core";
import { createRebaseClient } from "@rebasepro/client";
import { collections } from "virtual:rebase-collections";
// The client connects to your backend API and WebSocket
const rebaseClient = createRebaseClient({
baseUrl: "http://localhost:3001",
websocketUrl: "ws://localhost:3001"
});
// Collections are imported via a Vite virtual module
// that reads from the shared/ directory
  • createRebaseClient — Creates the SDK client that handles HTTP requests, WebSocket connections, and auth token management
  • virtual:rebase-collections — A Vite plugin that auto-imports your shared collections at build time
  • ControllersuseBuildNavigationStateController, useBuildCollectionRegistryController, etc. — these configure routing, collection resolution, and UI configuration

The backend is a Node.js server built on Hono (a fast, lightweight HTTP framework). The entry point index.ts initializes everything:

import { initializeRebaseBackend } from "@rebasepro/backend";
import { Hono } from "hono";
const app = new Hono();
await initializeRebaseBackend({
app,
server,
collections,
driver: {
connection: db,
schema: { tables, enums, relations }
},
auth: {
jwtSecret: process.env.JWT_SECRET!,
google: { clientId: process.env.GOOGLE_CLIENT_ID },
},
storage: {
type: "local",
basePath: "./uploads"
},
history: true
});

initializeRebaseBackend sets up:

  • REST API routes at /api/data/* — auto-generated CRUD for each collection
  • Auth routes at /api/auth/* — signup, login, refresh, Google OAuth
  • Storage routes at /api/storage/* — file upload/download
  • WebSocket server — real-time entity sync via Postgres LISTEN/NOTIFY
  • History — audit trail recording on every entity change

Collections are the single source of truth for your data model. They are defined as TypeScript and consumed by both the frontend (for UI generation) and the backend (for schema generation and API routing).

import { EntityCollection } from "@rebasepro/types";
export const productsCollection: EntityCollection = {
slug: "products",
name: "Products",
dbPath: "products",
properties: {
name: { type: "string", name: "Name" },
price: { type: "number", name: "Price" }
}
};

The slug becomes the URL path in the admin UI and the REST API endpoint (/api/data/products). The dbPath maps to the PostgreSQL table name.

  1. You define collections in shared/
  2. The backend reads them to generate Drizzle schemas and mount REST routes
  3. The frontend reads them (via Vite plugin) to render tables, forms, and navigation
  4. The CLI reads them to generate migration files with rebase schema generate

Changes to collections propagate everywhere automatically.