This commit is contained in:
@@ -1,19 +1,19 @@
|
||||
# bknd starter: Cloudflare Vite Code-Only
|
||||
A fullstack React + Vite application with bknd integration, showcasing **code-only mode** and Cloudflare Workers deployment.
|
||||
# userbase starter: Cloudflare Vite Code-Only
|
||||
A fullstack React + Vite application with userbase integration, showcasing **code-only mode** and Cloudflare Workers deployment.
|
||||
|
||||
## Key Features
|
||||
|
||||
This example demonstrates a minimal, code-first approach to building with bknd:
|
||||
This example demonstrates a minimal, code-first approach to building with userbase:
|
||||
|
||||
### 💻 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.
|
||||
|
||||
### 🎯 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).
|
||||
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.userbase.io/usage/cli#generating-types-types) or [Vite plugin](https://docs.userbase.io/extending/plugins#synctypes).
|
||||
|
||||
### ⚡ Split Configuration Pattern
|
||||
- **`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)
|
||||
- **`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.
|
||||
|
||||
@@ -27,15 +27,15 @@ Inside of your project, you'll see the following folders and files:
|
||||
│ ├── app/ # React frontend application
|
||||
│ │ ├── App.tsx
|
||||
│ │ ├── routes/
|
||||
│ │ │ ├── admin.tsx # bknd Admin UI route
|
||||
│ │ │ ├── admin.tsx # userbase Admin UI route
|
||||
│ │ │ └── home.tsx # Example frontend route
|
||||
│ │ └── main.tsx
|
||||
│ └── worker/
|
||||
│ └── index.ts # Cloudflare Worker entry
|
||||
├── config.ts # bknd configuration with schema definition
|
||||
├── bknd.config.ts # CLI configuration with platform proxy
|
||||
├── config.ts # userbase configuration with schema definition
|
||||
├── userbase.config.ts # CLI configuration with platform proxy
|
||||
├── seed.ts # Optional: seed data for development
|
||||
├── vite.config.ts # Standard Vite config (no bknd plugins)
|
||||
├── vite.config.ts # Standard Vite config (no userbase plugins)
|
||||
├── package.json
|
||||
└── wrangler.json # Cloudflare Workers configuration
|
||||
```
|
||||
@@ -49,7 +49,7 @@ Inside of your project, you'll see the following folders and files:
|
||||
|
||||
## 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 `userbase/ui` with `withProvider={{ user }}` so it respects the authenticated user returned by `useAuth`.
|
||||
- `/` showcases `useEntityQuery("todos")`, mutation helpers, and authentication state — demonstrating how manually declared types flow into the React code.
|
||||
|
||||
|
||||
@@ -59,9 +59,9 @@ Inside of your project, you'll see the following folders and files:
|
||||
The main configuration file that uses the `code()` mode helper:
|
||||
|
||||
```typescript
|
||||
import type { CloudflareBkndConfig } from "bknd/adapter/cloudflare";
|
||||
import { code } from "bknd/modes";
|
||||
import { boolean, em, entity, text } from "bknd";
|
||||
import type { CloudflareUserbaseConfig } from "userbase/adapter/cloudflare";
|
||||
import { code } from "userbase/modes";
|
||||
import { boolean, em, entity, text } from "userbase";
|
||||
|
||||
// define your schema using a Drizzle-like API
|
||||
const schema = em({
|
||||
@@ -74,11 +74,11 @@ const schema = em({
|
||||
// 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" {
|
||||
declare module "userbase" {
|
||||
interface DB extends Database {}
|
||||
}
|
||||
|
||||
export default code<CloudflareBkndConfig>({
|
||||
export default code<CloudflareUserbaseConfig>({
|
||||
app: (env) => ({
|
||||
config: {
|
||||
// convert schema to JSON format
|
||||
@@ -101,20 +101,20 @@ export default code<CloudflareBkndConfig>({
|
||||
```
|
||||
|
||||
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"`
|
||||
- **No auto-generated files**: No `userbase-config.json`, `userbase-types.d.ts`, or `.env.example`
|
||||
- **Manual type declaration**: Types are declared inline using `declare module "userbase"`
|
||||
- **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)
|
||||
- **CLI**: `npm run userbase -- types` (requires adding `typesFilePath` to config)
|
||||
- **Plugin**: Import `syncTypes` plugin and configure it in your app
|
||||
|
||||
### `bknd.config.ts`
|
||||
### `userbase.config.ts`
|
||||
Wraps the configuration for CLI usage with Cloudflare bindings:
|
||||
|
||||
```typescript
|
||||
import { withPlatformProxy } from "bknd/adapter/cloudflare/proxy";
|
||||
import { withPlatformProxy } from "userbase/adapter/cloudflare/proxy";
|
||||
import config from "./config.ts";
|
||||
|
||||
export default withPlatformProxy(config, {
|
||||
@@ -122,10 +122,10 @@ export default withPlatformProxy(config, {
|
||||
});
|
||||
```
|
||||
|
||||
**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.
|
||||
**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 userbase CLI.
|
||||
|
||||
### `vite.config.ts`
|
||||
Standard Vite configuration without bknd-specific plugins:
|
||||
Standard Vite configuration without userbase-specific plugins:
|
||||
|
||||
```typescript
|
||||
import { defineConfig } from "vite";
|
||||
@@ -149,8 +149,8 @@ All commands are run from the root of the project, from a terminal:
|
||||
| `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:seed`| Seeds the database with example data |
|
||||
| `npm run userbase` | Runs userbase CLI commands |
|
||||
| `npm run userbase: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 |
|
||||
|
||||
@@ -180,7 +180,7 @@ All commands are run from the root of the project, from a terminal:
|
||||
4. **Manually declare types** (optional, but recommended for IDE support):
|
||||
```typescript
|
||||
type Database = (typeof schema)["DB"];
|
||||
declare module "bknd" {
|
||||
declare module "userbase" {
|
||||
interface DB extends Database {}
|
||||
}
|
||||
```
|
||||
@@ -198,7 +198,7 @@ All commands are run from the root of the project, from a terminal:
|
||||
npm run dev
|
||||
|
||||
# Production database (safe operations only)
|
||||
CLOUDFLARE_ENV=production npm run bknd -- sync --force
|
||||
CLOUDFLARE_ENV=production npm run userbase -- sync --force
|
||||
```
|
||||
|
||||
## Before You Deploy
|
||||
@@ -253,7 +253,7 @@ This will:
|
||||
3. Sync the database schema (safe operations only)
|
||||
4. Deploy to Cloudflare Workers using Wrangler
|
||||
|
||||
In production, bknd will:
|
||||
In production, userbase will:
|
||||
- Use the configuration defined in `config.ts`
|
||||
- Skip config validation for better performance
|
||||
- Expect secrets to be provided via environment variables
|
||||
@@ -261,7 +261,7 @@ In production, bknd will:
|
||||
## How Code Mode Works
|
||||
|
||||
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
|
||||
2. **Convert to JSON:** Use `schema.toJSON()` to convert your schema to userbase'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
|
||||
|
||||
@@ -270,7 +270,7 @@ In production, bknd will:
|
||||
| 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` |
|
||||
| Configuration Files | None (all in code) | Auto-generated `userbase-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 |
|
||||
@@ -289,7 +289,7 @@ Add `typesFilePath` to your config:
|
||||
|
||||
```typescript
|
||||
export default code({
|
||||
typesFilePath: "./bknd-types.d.ts",
|
||||
typesFilePath: "./userbase-types.d.ts",
|
||||
// ... rest of config
|
||||
});
|
||||
```
|
||||
@@ -297,7 +297,7 @@ export default code({
|
||||
For Cloudflare Workers, you'll need the `devFsVitePlugin`:
|
||||
```typescript
|
||||
// vite.config.ts
|
||||
import { devFsVitePlugin } from "bknd/adapter/cloudflare";
|
||||
import { devFsVitePlugin } from "userbase/adapter/cloudflare";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
@@ -311,7 +311,7 @@ Finally, add the generated types to your `tsconfig.json`:
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"types": ["./bknd-types.d.ts"]
|
||||
"types": ["./userbase-types.d.ts"]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -323,26 +323,26 @@ This provides filesystem access for auto-syncing types despite Cloudflare's `une
|
||||
You may also use the CLI to generate types:
|
||||
|
||||
```sh
|
||||
npx bknd types --outfile ./bknd-types.d.ts
|
||||
npx userbase types --outfile ./userbase-types.d.ts
|
||||
```
|
||||
|
||||
## Database Seeding
|
||||
|
||||
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.
|
||||
Unlike UI-only and hybrid modes where userbase 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 userbase can't determine if the database is empty without additional queries, which would impact performance.
|
||||
|
||||
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:
|
||||
This example includes a [`seed.ts`](./seed.ts) file that you can run manually. For Cloudflare, it uses `userbase.config.ts` (with `withPlatformProxy`) to access Cloudflare resources like D1 during CLI execution:
|
||||
|
||||
```sh
|
||||
npm run bknd:seed
|
||||
npm run userbase:seed
|
||||
```
|
||||
|
||||
The seed script manually checks if the database is empty before inserting data. See the [seed.ts](./seed.ts) file for implementation details.
|
||||
|
||||
## Want to Learn More?
|
||||
|
||||
- [Cloudflare Integration Documentation](https://docs.bknd.io/integration/cloudflare)
|
||||
- [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)
|
||||
- [Cloudflare Integration Documentation](https://docs.userbase.io/integration/cloudflare)
|
||||
- [Code Mode Guide](https://docs.userbase.io/usage/introduction#code-only-mode)
|
||||
- [Mode Helpers Documentation](https://docs.userbase.io/usage/introduction#mode-helpers)
|
||||
- [Data Structure & Schema API](https://docs.userbase.io/usage/database#data-structure)
|
||||
- [Discord Community](https://discord.gg/952SFk8Tb8)
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* This file gets automatically picked up by the bknd CLI. Since we're using cloudflare,
|
||||
* This file gets automatically picked up by the userbase CLI. Since we're using cloudflare,
|
||||
* we want to use cloudflare bindings (such as the database). To do this, we need to wrap
|
||||
* the configuration with the `withPlatformProxy` helper function.
|
||||
*
|
||||
* Don't import this file directly in your app, otherwise "wrangler" will be bundled with your worker.
|
||||
* That's why we split the configuration into two files: `bknd.config.ts` and `config.ts`.
|
||||
* That's why we split the configuration into two files: `userbase.config.ts` and `config.ts`.
|
||||
*/
|
||||
|
||||
import { withPlatformProxy } from "bknd/adapter/cloudflare/proxy";
|
||||
import { withPlatformProxy } from "userbase/adapter/cloudflare/proxy";
|
||||
import config from "./config.ts";
|
||||
|
||||
export default withPlatformProxy(config, {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/// <reference types="./worker-configuration.d.ts" />
|
||||
|
||||
import type { CloudflareBkndConfig } from "bknd/adapter/cloudflare";
|
||||
import { code } from "bknd/modes";
|
||||
import { boolean, em, entity, text } from "bknd";
|
||||
import type { CloudflareUserbaseConfig } from "userbase/adapter/cloudflare";
|
||||
import { code } from "userbase/modes";
|
||||
import { boolean, em, entity, text } from "userbase";
|
||||
|
||||
const schema = em({
|
||||
todos: entity("todos", {
|
||||
@@ -13,13 +13,13 @@ const schema = em({
|
||||
|
||||
// register your schema to get automatic type completion
|
||||
// alternatively, you can use the CLI to generate types
|
||||
// learn more at https://docs.bknd.io/usage/cli/#generating-types-types
|
||||
// learn more at https://docs.userbase.io/usage/cli/#generating-types-types
|
||||
type Database = (typeof schema)["DB"];
|
||||
declare module "bknd" {
|
||||
declare module "userbase" {
|
||||
interface DB extends Database {}
|
||||
}
|
||||
|
||||
export default code<CloudflareBkndConfig>({
|
||||
export default code<CloudflareUserbaseConfig>({
|
||||
app: (env) => ({
|
||||
config: {
|
||||
data: schema.toJSON(),
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>bknd + Vite + Cloudflare + React + TS</title>
|
||||
<title>userbase + Vite + Cloudflare + React + TS</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
{
|
||||
"name": "cloudflare-vite-fullstack-bknd-code",
|
||||
"description": "A template for building a React application with Vite, Cloudflare Workers, and bknd",
|
||||
"name": "cloudflare-vite-fullstack-userbase-code",
|
||||
"description": "A template for building a React application with Vite, Cloudflare Workers, and userbase",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"cf:types": "wrangler types",
|
||||
"bknd": "node --experimental-strip-types node_modules/.bin/bknd",
|
||||
"bknd:seed": "NODE_NO_WARNINGS=1 node --experimental-strip-types seed.ts",
|
||||
"userbase": "node --experimental-strip-types node_modules/.bin/userbase",
|
||||
"userbase:seed": "NODE_NO_WARNINGS=1 node --experimental-strip-types seed.ts",
|
||||
"check": "tsc && vite build && wrangler deploy --dry-run",
|
||||
"deploy": "CLOUDFLARE_ENV=production vite build && CLOUDFLARE_ENV=production npm run bknd -- sync --force && wrangler deploy",
|
||||
"deploy": "CLOUDFLARE_ENV=production vite build && CLOUDFLARE_ENV=production npm run userbase -- sync --force && wrangler deploy",
|
||||
"dev": "vite",
|
||||
"preview": "npm run build && vite preview",
|
||||
"postinstall": "npm run cf:types && npm run bknd:seed"
|
||||
"postinstall": "npm run cf:types && npm run userbase:seed"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"bknd": "file:../../app",
|
||||
"userbase": "file:../../app",
|
||||
"hono": "4.10.6",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/// <reference types="./worker-configuration.d.ts" />
|
||||
|
||||
import { createFrameworkApp } from "bknd/adapter";
|
||||
import config from "./bknd.config.ts";
|
||||
import { createFrameworkApp } from "userbase/adapter";
|
||||
import config from "./userbase.config.ts";
|
||||
|
||||
const app = await createFrameworkApp(config, {});
|
||||
|
||||
@@ -15,12 +15,12 @@ const {
|
||||
// only run if the database is empty
|
||||
if (usersCount === 0 && todosCount === 0) {
|
||||
await app.em.mutator("todos").insertMany([
|
||||
{ title: "Learn bknd", done: true },
|
||||
{ title: "Learn userbase", done: true },
|
||||
{ title: "Build something cool", done: false },
|
||||
]);
|
||||
|
||||
await app.module.auth.createUser({
|
||||
email: "test@bknd.io",
|
||||
email: "test@userbase.io",
|
||||
password: "12345678",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Router, Switch, Route } from "wouter";
|
||||
import Home from "./routes/home.tsx";
|
||||
import { lazy, Suspense, useEffect, useState } from "react";
|
||||
const Admin = lazy(() => import("./routes/admin.tsx"));
|
||||
import { useAuth } from "bknd/client";
|
||||
import { useAuth } from "userbase/client";
|
||||
|
||||
export default function App() {
|
||||
const auth = useAuth();
|
||||
|
||||
@@ -2,7 +2,7 @@ import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import "./index.css";
|
||||
import App from "./App.tsx";
|
||||
import { ClientProvider } from "bknd/client";
|
||||
import { ClientProvider } from "userbase/client";
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Admin, type BkndAdminProps } from "bknd/ui";
|
||||
import "bknd/dist/styles.css";
|
||||
import { useAuth } from "bknd/client";
|
||||
import { Admin, type UserbaseAdminProps } from "userbase/ui";
|
||||
import "userbase/dist/styles.css";
|
||||
import { useAuth } from "userbase/client";
|
||||
|
||||
export default function AdminPage(props: BkndAdminProps) {
|
||||
export default function AdminPage(props: UserbaseAdminProps) {
|
||||
const auth = useAuth();
|
||||
return <Admin {...props} withProvider={{ user: auth.user }} />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useAuth, useEntityQuery } from "bknd/client";
|
||||
import bkndLogo from "../assets/bknd.svg";
|
||||
import { useAuth, useEntityQuery } from "userbase/client";
|
||||
import userbaseLogo from "../assets/userbase.svg";
|
||||
import cloudflareLogo from "../assets/cloudflare.svg";
|
||||
import viteLogo from "../assets/vite.svg";
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function Home() {
|
||||
return (
|
||||
<div className="flex-col gap-10 max-w-96 mx-auto w-full min-h-full flex justify-center items-center">
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<img src={bkndLogo} alt="bknd" className="w-48 dark:invert" />
|
||||
<img src={userbaseLogo} alt="userbase" className="w-48 dark:invert" />
|
||||
<div className="font-mono opacity-70">&</div>
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<img src={cloudflareLogo} alt="cloudflare" className="h-10" />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { serve } from "bknd/adapter/cloudflare";
|
||||
import { serve } from "userbase/adapter/cloudflare";
|
||||
import config from "../../config.ts";
|
||||
|
||||
export default serve(config, () => ({
|
||||
// since bknd is running code-only, we can use a pre-initialized app instance if available
|
||||
// since userbase is running code-only, we can use a pre-initialized app instance if available
|
||||
warm: true,
|
||||
}));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "node_modules/wrangler/config-schema.json",
|
||||
"name": "cloudflare-vite-fullstack-bknd",
|
||||
"name": "cloudflare-vite-fullstack-userbase",
|
||||
"main": "./src/worker/index.ts",
|
||||
"compatibility_date": "2025-10-08",
|
||||
"compatibility_flags": ["nodejs_compat"],
|
||||
|
||||
Reference in New Issue
Block a user