Files
shishantbiswas 0bfcb5ad71
Run Tests / test (pull_request) Successful in 4m57s
bknd is dead long live userbase
2026-09-27 19:34:06 +05:30

9.3 KiB

userbase starter: Cloudflare Vite Hybrid

A fullstack React + Vite application with userbase integration, showcasing hybrid mode and Cloudflare Workers deployment.

Key Features

This example demonstrates several advanced userbase features:

🔄 Hybrid Mode

Configure your backend visually in development using the Admin UI, then automatically switch to code-only mode in production for maximum performance. Changes made in the Admin UI are automatically synced to userbase-config.json and type definitions are generated in userbase-types.d.ts.

📁 Filesystem Access with Vite Plugin

Cloudflare's Vite plugin uses unenv which disables Node.js APIs like fs. This example uses userbase's devFsVitePlugin and devFsWrite to provide filesystem access during development, enabling automatic syncing of types and configuration.

⚡ Split Configuration Pattern

  • config.ts: Shared configuration that can be safely imported in your worker
  • userbase.config.ts: Wraps the configuration with withPlatformProxy for CLI usage with Cloudflare bindings (should NOT be imported in your worker)

This pattern prevents bundling wrangler into your worker while still allowing CLI access to Cloudflare resources.

Project Structure

Inside of your project, you'll see the following folders and files:

/
├── src/
│   ├── app/              # React frontend application
│   │   ├── App.tsx
│   │   ├── routes/
│   │   │   ├── admin.tsx # userbase Admin UI route
│   │   │   └── home.tsx  # Example frontend route
│   │   └── main.tsx
│   └── worker/
│       └── index.ts      # Cloudflare Worker entry
├── config.ts             # Shared userbase configuration (hybrid mode)
├── userbase.config.ts        # CLI configuration with platform proxy
├── userbase-config.json      # Auto-generated production config
├── userbase-types.d.ts       # Auto-generated TypeScript types
├── .env.example          # Auto-generated secrets template
├── vite.config.ts        # Includes devFsVitePlugin
├── package.json
└── wrangler.json         # Cloudflare Workers configuration

Cloudflare resources

  • D1: wrangler.json declares a DB binding. In production, replace database_id with your own (wrangler d1 create <name>).
  • R2: Optional BUCKET binding is pre-configured to show how to add additional services.
  • Environment awareness: ENVIRONMENT switch toggles hybrid behavior: production makes the database read-only, while development keeps mode: "db" and auto-syncs schema.
  • Static assets: The Assets binding points to dist/client. Run npm run build before wrangler deploy to upload the client bundle alongside the worker.

Admin UI & frontend

  • /admin mounts <Admin /> from userbase/ui with withProvider={{ user }} so it respects the authenticated user returned by useAuth.
  • / showcases useEntityQuery("todos"), mutation helpers, and authentication state — demonstrating how the generated client types (userbase-types.d.ts) flow into the React code.

Configuration Files

config.ts

The main configuration file that uses the hybrid() mode helper:

  • Loads the generated config via an ESM reader (importing ./userbase-config.json).
  • Uses devFsWrite as the writer so the CLI/plugin can persist files even though Node's fs API is unavailable in Miniflare.
  • Sets typesFilePath, configFilePath, and syncSecrets (writes .env.example) so config, types, and secret placeholders stay aligned.
  • Seeds example data/users in options.seed when the database is empty.
  • Disables the built-in admin controller because the React app renders /admin via userbase/ui.
import { hybrid } from "userbase/modes";
import { devFsWrite, type CloudflareUserbaseConfig } from "userbase/adapter/cloudflare";

export default hybrid<CloudflareUserbaseConfig>({
   // Special reader for Cloudflare Workers (no Node.js fs)
   reader: async () => (await import("./userbase-config.json")).default,
   // devFsWrite enables file writing via Vite plugin
   writer: devFsWrite,
   // Auto-sync these files in development
   typesFilePath: "./userbase-types.d.ts",
   configFilePath: "./userbase-config.json",
   syncSecrets: {
      enabled: true,
      outFile: ".env.example",
      format: "env",
   },
   app: (env) => ({
      adminOptions: false, // Disabled - we render React app instead
      isProduction: env.ENVIRONMENT === "production",
      secrets: env,
      // ... your configuration
   }),
});

userbase.config.ts

Wraps the configuration for CLI usage with Cloudflare bindings:

import { withPlatformProxy } from "userbase/adapter/cloudflare/proxy";
import config from "./config.ts";

export default withPlatformProxy(config);

vite.config.ts

Includes the devFsVitePlugin for filesystem access:

import { devFsVitePlugin } from "userbase/adapter/cloudflare";

export default defineConfig({
   plugins: [
      // ...
      devFsVitePlugin({ configFile: "config.ts" }),
      cloudflare(),
   ],
});

Commands

All commands are run from the root of the project, from a terminal:

Command Action
npm install Installs dependencies and generates wrangler types
npm run dev Starts local dev server with Vite at localhost:5173
npm run build Builds the application for production
npm run preview Builds and previews the production build locally
npm run deploy Builds, syncs the schema and deploys to Cloudflare Workers
npm run userbase Runs userbase CLI commands
npm run userbase:types Generates TypeScript types from your schema
npm run cf:types Generates Cloudflare Worker types from wrangler.json
npm run check Type checks and does a dry-run deployment

Development Workflow

  1. Install dependencies:

    npm install
    
  2. Start development server:

    npm run dev
    
  3. Visit the Admin UI at http://localhost:5173/admin to configure your backend visually:

    • Create entities and fields
    • Configure authentication
    • Set up relationships
    • Define permissions
  4. Watch for auto-generated files:

    • userbase-config.json - Production configuration
    • userbase-types.d.ts - TypeScript types
    • .env.example - Required secrets
  5. Use the CLI for manual operations:

    # Generate types manually
    npm run userbase:types
    
    # Sync the production database schema (only safe operations are applied)
    CLOUDFLARE_ENV=production npm run userbase -- sync --force
    

Before you deploy

If you're using a D1 database, make sure to create a database in your Cloudflare account and replace the database_id accordingly in wrangler.json:

npx wrangler d1 create my-database

Update wrangler.json:

{
   "d1_databases": [
      {
         "binding": "DB",
         "database_name": "my-database",
         "database_id": "your-database-id-here"
      }
   ]
}

Deployment

Deploy to Cloudflare Workers:

npm run deploy

This will:

  1. Set ENVIRONMENT=production to activate code-only mode
  2. Build the Vite application
  3. Deploy to Cloudflare Workers using Wrangler

In production, userbase will:

  • Use the configuration from userbase-config.json (read-only)
  • Skip config validation for better performance
  • Expect secrets to be provided via environment variables

Environment Variables

Make sure to set your secrets in the Cloudflare Workers dashboard or via Wrangler:

# Example: Set JWT secret
npx wrangler secret put auth.jwt.secret

Check .env.example for all required secrets after running the app in development mode.

How Hybrid Mode Works

graph LR
    A[Development] -->|Visual Config| B[Admin UI]
    B -->|Auto-sync| C[userbase-config.json]
    B -->|Auto-sync| D[userbase-types.d.ts]
    C -->|Deploy| E[Production]
    E -->|Read-only| F[Code-only Mode]
  1. In Development: mode: "db" - Configuration stored in database, editable via Admin UI
  2. Auto-sync: Changes automatically written to userbase-config.json and types to userbase-types.d.ts
  3. In Production: mode: "code" - Configuration read from userbase-config.json, no database overhead

Why devFsVitePlugin?

Cloudflare's Vite plugin removes Node.js APIs for Workers compatibility. This breaks filesystem operations needed for:

  • Auto-syncing TypeScript types (syncTypes plugin)
  • Auto-syncing configuration (syncConfig plugin)
  • Auto-syncing secrets (syncSecrets plugin)

The devFsVitePlugin + devFsWrite combination provides a workaround by using Vite's module system to enable file writes during development.

Want to learn more?