bknd starter: Cloudflare Vite Hybrid
A fullstack React + Vite application with bknd integration, showcasing hybrid mode and Cloudflare Workers deployment.
Key Features
This example demonstrates several advanced bknd 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 bknd-config.json and type definitions are generated in bknd-types.d.ts.
📁 Filesystem Access with Vite Plugin
Cloudflare's Vite plugin uses unenv which disables Node.js APIs like fs. This example uses bknd'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 workerbknd.config.ts: Wraps the configuration withwithPlatformProxyfor 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 # bknd Admin UI route
│ │ │ └── home.tsx # Example frontend route
│ │ └── main.tsx
│ └── worker/
│ └── index.ts # Cloudflare Worker entry
├── config.ts # Shared bknd configuration (hybrid mode)
├── bknd.config.ts # CLI configuration with platform proxy
├── bknd-config.json # Auto-generated production config
├── bknd-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.jsondeclares aDBbinding. In production, replacedatabase_idwith your own (wrangler d1 create <name>). - R2: Optional
BUCKETbinding is pre-configured to show how to add additional services. - Environment awareness:
ENVIRONMENTswitch toggles hybrid behavior: production makes the database read-only, while development keepsmode: "db"and auto-syncs schema. - Static assets: The Assets binding points to
dist/client. Runnpm run buildbeforewrangler deployto upload the client bundle alongside the worker.
Admin UI & frontend
/adminmounts<Admin />frombknd/uiwithwithProvider={{ user }}so it respects the authenticated user returned byuseAuth./showcasesuseEntityQuery("todos"), mutation helpers, and authentication state — demonstrating how the generated client types (bknd-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./bknd-config.json). - Uses
devFsWriteas thewriterso the CLI/plugin can persist files even though Node'sfsAPI is unavailable in Miniflare. - Sets
typesFilePath,configFilePath, andsyncSecrets(writes.env.example) so config, types, and secret placeholders stay aligned. - Seeds example data/users in
options.seedwhen the database is empty. - Disables the built-in admin controller because the React app renders
/adminviabknd/ui.
import { hybrid } from "bknd/modes";
import { devFsWrite, type CloudflareBkndConfig } from "bknd/adapter/cloudflare";
export default hybrid<CloudflareBkndConfig>({
// Special reader for Cloudflare Workers (no Node.js fs)
reader: async () => (await import("./bknd-config.json")).default,
// devFsWrite enables file writing via Vite plugin
writer: devFsWrite,
// Auto-sync these files in development
typesFilePath: "./bknd-types.d.ts",
configFilePath: "./bknd-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
}),
});
bknd.config.ts
Wraps the configuration for CLI usage with Cloudflare bindings:
import { withPlatformProxy } from "bknd/adapter/cloudflare/proxy";
import config from "./config.ts";
export default withPlatformProxy(config);
vite.config.ts
Includes the devFsVitePlugin for filesystem access:
import { devFsVitePlugin } from "bknd/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 bknd |
Runs bknd CLI commands |
npm run bknd: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
-
Install dependencies:
npm install -
Start development server:
npm run dev -
Visit the Admin UI at
http://localhost:5173/adminto configure your backend visually:- Create entities and fields
- Configure authentication
- Set up relationships
- Define permissions
-
Watch for auto-generated files:
bknd-config.json- Production configurationbknd-types.d.ts- TypeScript types.env.example- Required secrets
-
Use the CLI for manual operations:
# Generate types manually npm run bknd:types # Sync the production database schema (only safe operations are applied) CLOUDFLARE_ENV=production npm run bknd -- 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:
- Set
ENVIRONMENT=productionto activate code-only mode - Build the Vite application
- Deploy to Cloudflare Workers using Wrangler
In production, bknd will:
- Use the configuration from
bknd-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[bknd-config.json]
B -->|Auto-sync| D[bknd-types.d.ts]
C -->|Deploy| E[Production]
E -->|Read-only| F[Code-only Mode]
- In Development:
mode: "db"- Configuration stored in database, editable via Admin UI - Auto-sync: Changes automatically written to
bknd-config.jsonand types tobknd-types.d.ts - In Production:
mode: "code"- Configuration read frombknd-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 (
syncTypesplugin) - Auto-syncing configuration (
syncConfigplugin) - Auto-syncing secrets (
syncSecretsplugin)
The devFsVitePlugin + devFsWrite combination provides a workaround by using Vite's module system to enable file writes during development.