update cloudflare-vite-code readme to reflect code-only mode

The template documentation now emphasizes code-first approach with manual type management instead of automatic file generation. Includes updated configuration examples, deployment instructions, and a new comparison table between code and hybrid modes.
This commit is contained in:
dswbx
2025-11-21 18:18:56 +01:00
parent 5d6046b149
commit a39407c9a3

View File

@@ -1,18 +1,18 @@
# bknd starter: Cloudflare Vite Hybrid # bknd starter: Cloudflare Vite Code-Only
A fullstack React + Vite application with bknd integration, showcasing **hybrid mode** and Cloudflare Workers deployment. A fullstack React + Vite application with bknd integration, showcasing **code-only mode** and Cloudflare Workers deployment.
## Key Features ## Key Features
This example demonstrates several advanced bknd features: This example demonstrates a minimal, code-first approach to building with bknd:
### 🔄 Hybrid Mode ### 💻 Code-Only 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`. Define your entire backend **programmatically** using a Drizzle-like API. Your data structure, authentication, and configuration live directly in code with zero build-time tooling required. Perfect for developers who prefer traditional code-first workflows.
### 📁 Filesystem Access with Vite Plugin ### 🎯 Minimal Boilerplate
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. Unlike the hybrid mode template, this example uses **no automatic type generation**, **no filesystem plugins**, and **no auto-synced configuration files**. This simulates a typical development environment where you manage types generation manually. If you prefer automatic type generation, you can easily add it using the [CLI](https://docs.bknd.io/usage/cli#generating-types-types) or [Vite plugin](https://docs.bknd.io/extending/plugins#synctypes).
### ⚡ Split Configuration Pattern ### ⚡ Split Configuration Pattern
- **`config.ts`**: Shared configuration that can be safely imported in your worker - **`config.ts`**: Main configuration that defines your schema and can be safely imported in your worker
- **`bknd.config.ts`**: Wraps the configuration with `withPlatformProxy` for CLI usage with Cloudflare bindings (should NOT be imported in your worker) - **`bknd.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. This pattern prevents bundling `wrangler` into your worker while still allowing CLI access to Cloudflare resources.
@@ -32,67 +32,84 @@ Inside of your project, you'll see the following folders and files:
│ │ └── main.tsx │ │ └── main.tsx
│ └── worker/ │ └── worker/
│ └── index.ts # Cloudflare Worker entry │ └── index.ts # Cloudflare Worker entry
├── config.ts # Shared bknd configuration (hybrid mode) ├── config.ts # bknd configuration with schema definition
├── bknd.config.ts # CLI configuration with platform proxy ├── bknd.config.ts # CLI configuration with platform proxy
├── bknd-config.json # Auto-generated production config ├── seed.ts # Optional: seed data for development
├── bknd-types.d.ts # Auto-generated TypeScript types ├── vite.config.ts # Standard Vite config (no bknd plugins)
├── .env.example # Auto-generated secrets template
├── vite.config.ts # Includes devFsVitePlugin
├── package.json ├── package.json
└── wrangler.json # Cloudflare Workers configuration └── wrangler.json # Cloudflare Workers configuration
``` ```
## Cloudflare resources ## Cloudflare Resources
- **D1:** `wrangler.json` declares a `DB` binding. In production, replace `database_id` with your own (`wrangler d1 create <name>`). - **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. - **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. - **Environment awareness:** `ENVIRONMENT` variable determines whether to sync the database schema automatically (development only).
- **Static assets:** The Assets binding points to `dist/client`. Run `npm run build` before `wrangler deploy` to upload the client bundle alongside the worker. - **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 UI & Frontend
- `/admin` mounts `<Admin />` from `bknd/ui` with `withProvider={{ user }}` so it respects the authenticated user returned by `useAuth`. - `/admin` mounts `<Admin />` from `bknd/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 (`bknd-types.d.ts`) flow into the React code. - `/` showcases `useEntityQuery("todos")`, mutation helpers, and authentication state — demonstrating how manually declared types flow into the React code.
## Configuration Files ## Configuration Files
### `config.ts` ### `config.ts`
The main configuration file that uses the `hybrid()` mode helper: The main configuration file that uses the `code()` mode helper:
- Loads the generated config via an ESM `reader` (importing `./bknd-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 `bknd/ui`.
```typescript ```typescript
import { hybrid } from "bknd/modes"; import type { CloudflareBkndConfig } from "bknd/adapter/cloudflare";
import { devFsWrite, type CloudflareBkndConfig } from "bknd/adapter/cloudflare"; import { code } from "bknd/modes";
import { boolean, em, entity, text } from "bknd";
export default hybrid<CloudflareBkndConfig>({ // define your schema using a Drizzle-like API
// Special reader for Cloudflare Workers (no Node.js fs) const schema = em({
reader: async () => (await import("./bknd-config.json")).default, todos: entity("todos", {
// devFsWrite enables file writing via Vite plugin title: text(),
writer: devFsWrite, done: boolean(),
// Auto-sync these files in development }),
typesFilePath: "./bknd-types.d.ts", });
configFilePath: "./bknd-config.json",
syncSecrets: { // register your schema for type completion (optional)
enabled: true, // alternatively, you can use the CLI to auto-generate types
outFile: ".env.example", type Database = (typeof schema)["DB"];
format: "env", declare module "bknd" {
}, interface DB extends Database {}
}
export default code<CloudflareBkndConfig>({
app: (env) => ({ app: (env) => ({
adminOptions: false, // Disabled - we render React app instead config: {
// convert schema to JSON format
data: schema.toJSON(),
auth: {
enabled: true,
jwt: {
// secrets are directly passed to the config
secret: env.JWT_SECRET,
issuer: "cloudflare-vite-code-example",
},
},
},
// disable the built-in admin controller (we render our own app)
adminOptions: false,
// determines whether the database should be automatically synced
isProduction: env.ENVIRONMENT === "production", isProduction: env.ENVIRONMENT === "production",
secrets: env,
// ... your configuration
}), }),
}); });
``` ```
Key differences from hybrid mode:
- **No auto-generated files**: No `bknd-config.json`, `bknd-types.d.ts`, or `.env.example`
- **Manual type declaration**: Types are declared inline using `declare module "bknd"`
- **Direct secret access**: Secrets come directly from `env` parameters
- **Simpler setup**: No filesystem plugins or readers/writers needed
If you prefer automatic type generation, you can add it later using:
- **CLI**: `npm run bknd -- types` (requires adding `typesFilePath` to config)
- **Plugin**: Import `syncTypes` plugin and configure it in your app
### `bknd.config.ts` ### `bknd.config.ts`
Wraps the configuration for CLI usage with Cloudflare bindings: Wraps the configuration for CLI usage with Cloudflare bindings:
@@ -100,21 +117,24 @@ Wraps the configuration for CLI usage with Cloudflare bindings:
import { withPlatformProxy } from "bknd/adapter/cloudflare/proxy"; import { withPlatformProxy } from "bknd/adapter/cloudflare/proxy";
import config from "./config.ts"; import config from "./config.ts";
export default withPlatformProxy(config); export default withPlatformProxy(config, {
useProxy: true,
});
``` ```
**Important**: Don't import this file in your worker, as it would bundle `wrangler` into your production code. This file is only used by the bknd CLI.
### `vite.config.ts` ### `vite.config.ts`
Includes the `devFsVitePlugin` for filesystem access: Standard Vite configuration without bknd-specific plugins:
```typescript ```typescript
import { devFsVitePlugin } from "bknd/adapter/cloudflare"; import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { cloudflare } from "@cloudflare/vite-plugin";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [react(), tailwindcss(), cloudflare()],
// ...
devFsVitePlugin({ configFile: "config.ts" }),
cloudflare(),
],
}); });
``` ```
@@ -124,13 +144,13 @@ All commands are run from the root of the project, from a terminal:
| Command | Action | | Command | Action |
|:-------------------|:----------------------------------------------------------| |:-------------------|:----------------------------------------------------------|
| `npm install` | Installs dependencies and generates wrangler types | | `npm install` | Installs dependencies, generates types, and seeds database|
| `npm run dev` | Starts local dev server with Vite at `localhost:5173` | | `npm run dev` | Starts local dev server with Vite at `localhost:5173` |
| `npm run build` | Builds the application for production | | `npm run build` | Builds the application for production |
| `npm run preview` | Builds and previews the production build locally | | `npm run preview` | Builds and previews the production build locally |
| `npm run deploy` | Builds, syncs the schema and deploys to Cloudflare Workers| | `npm run deploy` | Builds, syncs the schema and deploys to Cloudflare Workers|
| `npm run bknd` | Runs bknd CLI commands | | `npm run bknd` | Runs bknd CLI commands |
| `npm run bknd:types` | Generates TypeScript types from your schema | | `npm run bknd:seed`| Seeds the database with example data |
| `npm run cf:types` | Generates Cloudflare Worker types from `wrangler.json` | | `npm run cf:types` | Generates Cloudflare Worker types from `wrangler.json` |
| `npm run check` | Type checks and does a dry-run deployment | | `npm run check` | Type checks and does a dry-run deployment |
@@ -140,41 +160,58 @@ All commands are run from the root of the project, from a terminal:
```sh ```sh
npm install npm install
``` ```
This will install dependencies, generate Cloudflare types, and seed the database.
2. **Start development server:** 2. **Start development server:**
```sh ```sh
npm run dev npm run dev
``` ```
3. **Visit the Admin UI** at `http://localhost:5173/admin` to configure your backend visually: 3. **Define your schema in code** (`config.ts`):
- Create entities and fields ```typescript
- Configure authentication const schema = em({
- Set up relationships todos: entity("todos", {
- Define permissions title: text(),
done: boolean(),
}),
});
```
4. **Watch for auto-generated files:** 4. **Manually declare types** (optional, but recommended for IDE support):
- `bknd-config.json` - Production configuration ```typescript
- `bknd-types.d.ts` - TypeScript types type Database = (typeof schema)["DB"];
- `.env.example` - Required secrets declare module "bknd" {
interface DB extends Database {}
}
```
5. **Use the CLI** for manual operations: 5. **Use the Admin UI** at `http://localhost:5173/admin` to:
```sh - View and manage your data
# Generate types manually - Monitor authentication
npm run bknd:types - Access database tools
# Sync the production database schema (only safe operations are applied) Note: In code mode, you cannot edit the schema through the UI. All schema changes must be done in `config.ts`.
6. **Sync schema changes** to your database:
```sh
# Local development (happens automatically on startup)
npm run dev
# Production database (safe operations only)
CLOUDFLARE_ENV=production npm run bknd -- sync --force CLOUDFLARE_ENV=production npm run bknd -- sync --force
``` ```
## Before you deploy ## 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`: ### 1. Create a D1 Database
Create a database in your Cloudflare account:
```sh ```sh
npx wrangler d1 create my-database npx wrangler d1 create my-database
``` ```
Update `wrangler.json`: Update `wrangler.json` with your database ID:
```json ```json
{ {
"d1_databases": [ "d1_databases": [
@@ -187,6 +224,21 @@ Update `wrangler.json`:
} }
``` ```
### 2. Set Required Secrets
Set your secrets in Cloudflare Workers:
```sh
# JWT secret (required for authentication)
npx wrangler secret put JWT_SECRET
```
You can generate a secure secret using:
```sh
# Using openssl
openssl rand -base64 64
```
## Deployment ## Deployment
Deploy to Cloudflare Workers: Deploy to Cloudflare Workers:
@@ -196,54 +248,101 @@ npm run deploy
``` ```
This will: This will:
1. Set `ENVIRONMENT=production` to activate code-only mode 1. Set `ENVIRONMENT=production` to prevent automatic schema syncing
2. Build the Vite application 2. Build the Vite application
3. Deploy to Cloudflare Workers using Wrangler 3. Sync the database schema (safe operations only)
4. Deploy to Cloudflare Workers using Wrangler
In production, bknd will: In production, bknd will:
- Use the configuration from `bknd-config.json` (read-only) - Use the configuration defined in `config.ts`
- Skip config validation for better performance - Skip config validation for better performance
- Expect secrets to be provided via environment variables - Expect secrets to be provided via environment variables
## Environment Variables ## How Code Mode Works
Make sure to set your secrets in the Cloudflare Workers dashboard or via Wrangler: 1. **Define Schema:** Create entities and fields using the Drizzle-like API in `config.ts`
2. **Convert to JSON:** Use `schema.toJSON()` to convert your schema to bknd's configuration format
3. **Manual Types:** Optionally declare types inline for IDE support and type safety
4. **Deploy:** Same configuration runs in both development and production
### Code Mode vs Hybrid Mode
| Feature | Code Mode | Hybrid Mode |
|---------|-----------|-------------|
| Schema Definition | Code-only (`em`, `entity`, `text`) | Visual UI in dev, code in prod |
| Configuration Files | None (all in code) | Auto-generated `bknd-config.json` |
| Type Generation | Manual or opt-in | Automatic |
| Setup Complexity | Minimal | Requires plugins & filesystem access |
| Use Case | Traditional code-first workflows | Rapid prototyping, visual development |
## Type Generation (Optional)
This example intentionally **does not use automatic type generation** to simulate a typical development environment where types are managed manually. This approach:
- Reduces build complexity
- Eliminates dependency on build-time tooling
- Works in any environment without special plugins
However, if you prefer automatic type generation, you can easily add it:
### Option 1: Using the Vite Plugin and `code` helper presets
Add `typesFilePath` to your config:
```typescript
export default code({
typesFilePath: "./bknd-types.d.ts",
// ... rest of config
});
```
For Cloudflare Workers, you'll need the `devFsVitePlugin`:
```typescript
// vite.config.ts
import { devFsVitePlugin } from "bknd/adapter/cloudflare";
export default defineConfig({
plugins: [
// ...
devFsVitePlugin({ configFile: "config.ts" })
],
});
```
Finally, add the generated types to your `tsconfig.json`:
```json
{
"compilerOptions": {
"types": ["./bknd-types.d.ts"]
}
}
```
This provides filesystem access for auto-syncing types despite Cloudflare's `unenv` restrictions.
### Option 2: Using the CLI
You may also use the CLI to generate types:
```sh ```sh
# Example: Set JWT secret npx bknd types --outfile ./bknd-types.d.ts
npx wrangler secret put auth.jwt.secret
``` ```
Check `.env.example` for all required secrets after running the app in development mode. ## Database Seeding
## How Hybrid Mode Works Unlike UI-only and hybrid modes where bknd can automatically detect an empty database (by attempting to fetch the configuration. A "table not found" error indicates a fresh database), **code mode requires manual seeding**. This is because in code mode, the configuration is always provided from code, so bknd can't determine if the database is empty without additional queries, which would impact performance.
```mermaid This example includes a [`seed.ts`](./seed.ts) file that you can run manually. For Cloudflare, it uses `bknd.config.ts` (with `withPlatformProxy`) to access Cloudflare resources like D1 during CLI execution:
graph LR
A[Development] -->|Visual Config| B[Admin UI] ```sh
B -->|Auto-sync| C[bknd-config.json] npm run bknd:seed
B -->|Auto-sync| D[bknd-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 The seed script manually checks if the database is empty before inserting data. See the [seed.ts](./seed.ts) file for implementation details.
2. **Auto-sync:** Changes automatically written to `bknd-config.json` and types to `bknd-types.d.ts`
3. **In Production:** `mode: "code"` - Configuration read from `bknd-config.json`, no database overhead
## Why devFsVitePlugin? ## Want to Learn More?
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?
- [Cloudflare Integration Documentation](https://docs.bknd.io/integration/cloudflare) - [Cloudflare Integration Documentation](https://docs.bknd.io/integration/cloudflare)
- [Hybrid Mode Guide](https://docs.bknd.io/usage/introduction#hybrid-mode) - [Code Mode Guide](https://docs.bknd.io/usage/introduction#code-only-mode)
- [Mode Helpers Documentation](https://docs.bknd.io/usage/introduction#mode-helpers) - [Mode Helpers Documentation](https://docs.bknd.io/usage/introduction#mode-helpers)
- [Data Structure & Schema API](https://docs.bknd.io/usage/database#data-structure)
- [Discord Community](https://discord.gg/952SFk8Tb8) - [Discord Community](https://discord.gg/952SFk8Tb8)