diff --git a/examples/cloudflare-vite-code/README.md b/examples/cloudflare-vite-code/README.md index 5f19c07..c54626f 100644 --- a/examples/cloudflare-vite-code/README.md +++ b/examples/cloudflare-vite-code/README.md @@ -1,18 +1,18 @@ -# bknd starter: Cloudflare Vite Hybrid -A fullstack React + Vite application with bknd integration, showcasing **hybrid mode** and Cloudflare Workers deployment. +# bknd starter: Cloudflare Vite Code-Only +A fullstack React + Vite application with bknd integration, showcasing **code-only mode** and Cloudflare Workers deployment. ## Key Features -This example demonstrates several advanced bknd features: +This example demonstrates a minimal, code-first approach to building with bknd: -### 🔄 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`. +### 💻 Code-Only Mode +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 -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. +### 🎯 Minimal Boilerplate +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 -- **`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) 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 │ └── worker/ │ └── 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.json # Auto-generated production config -├── bknd-types.d.ts # Auto-generated TypeScript types -├── .env.example # Auto-generated secrets template -├── vite.config.ts # Includes devFsVitePlugin +├── seed.ts # Optional: seed data for development +├── vite.config.ts # Standard Vite config (no bknd plugins) ├── package.json └── 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 `). - **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. -## Admin UI & frontend +## Admin UI & Frontend - `/admin` mounts `` 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 ### `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 `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`. - +The main configuration file that uses the `code()` mode helper: ```typescript -import { hybrid } from "bknd/modes"; -import { devFsWrite, type CloudflareBkndConfig } from "bknd/adapter/cloudflare"; +import type { CloudflareBkndConfig } from "bknd/adapter/cloudflare"; +import { code } from "bknd/modes"; +import { boolean, em, entity, text } from "bknd"; -export default hybrid({ - // 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", - }, +// define your schema using a Drizzle-like API +const schema = em({ + todos: entity("todos", { + title: text(), + done: boolean(), + }), +}); + +// register your schema for type completion (optional) +// alternatively, you can use the CLI to auto-generate types +type Database = (typeof schema)["DB"]; +declare module "bknd" { + interface DB extends Database {} +} + +export default code({ 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", - 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` 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 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` -Includes the `devFsVitePlugin` for filesystem access: +Standard Vite configuration without bknd-specific plugins: ```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({ - plugins: [ - // ... - devFsVitePlugin({ configFile: "config.ts" }), - cloudflare(), - ], + plugins: [react(), tailwindcss(), cloudflare()], }); ``` @@ -124,13 +144,13 @@ All commands are run from the root of the project, from a terminal: | 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 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 bknd:seed`| Seeds the database with example data | | `npm run cf:types` | Generates Cloudflare Worker types from `wrangler.json` | | `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 npm install ``` + This will install dependencies, generate Cloudflare types, and seed the database. 2. **Start development server:** ```sh 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 +3. **Define your schema in code** (`config.ts`): + ```typescript + const schema = em({ + todos: entity("todos", { + title: text(), + done: boolean(), + }), + }); + ``` -4. **Watch for auto-generated files:** - - `bknd-config.json` - Production configuration - - `bknd-types.d.ts` - TypeScript types - - `.env.example` - Required secrets +4. **Manually declare types** (optional, but recommended for IDE support): + ```typescript + type Database = (typeof schema)["DB"]; + declare module "bknd" { + interface DB extends Database {} + } + ``` -5. **Use the CLI** for manual operations: - ```sh - # Generate types manually - npm run bknd:types +5. **Use the Admin UI** at `http://localhost:5173/admin` to: + - View and manage your data + - Monitor authentication + - 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 ``` -## 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 npx wrangler d1 create my-database ``` -Update `wrangler.json`: +Update `wrangler.json` with your database ID: ```json { "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 Deploy to Cloudflare Workers: @@ -196,54 +248,101 @@ npm run deploy ``` 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 -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: -- Use the configuration from `bknd-config.json` (read-only) +- Use the configuration defined in `config.ts` - Skip config validation for better performance - 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 -# Example: Set JWT secret -npx wrangler secret put auth.jwt.secret +npx bknd types --outfile ./bknd-types.d.ts ``` -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 -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] +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: + +```sh +npm run bknd:seed ``` -1. **In Development:** `mode: "db"` - Configuration stored in database, editable via Admin UI -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 +The seed script manually checks if the database is empty before inserting data. See the [seed.ts](./seed.ts) file for implementation details. -## 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? +## Want to Learn More? - [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) +- [Data Structure & Schema API](https://docs.bknd.io/usage/database#data-structure) - [Discord Community](https://discord.gg/952SFk8Tb8)