bknd is dead long live userbase
Run Tests / test (pull_request) Successful in 4m57s

This commit is contained in:
2026-09-27 19:34:06 +05:30
parent 05e0086fcd
commit 0bfcb5ad71
529 changed files with 2385 additions and 2924 deletions
+4 -4
View File
@@ -1,5 +1,5 @@
# bknd starter: Astro
A minimal Astro project with bknd integration.
# userbase starter: Astro
A minimal Astro project with userbase integration.
## Project Structure
@@ -19,7 +19,7 @@ Inside of your Astro project, you'll see the following folders and files:
└── package.json
```
To update `bknd` config, check `src/api/[...api].astro` and `src/admin/[...admin].astro`.
To update `userbase` config, check `src/api/[...api].astro` and `src/admin/[...admin].astro`.
## Commands
@@ -36,4 +36,4 @@ All commands are run from the root of the project, from a terminal:
## Want to learn more?
Feel free to check [our documentation](https://docs.bknd.io/integration/astro) or jump into our [Discord server](https://discord.gg/952SFk8Tb8).
Feel free to check [our documentation](https://docs.userbase.io/integration/astro) or jump into our [Discord server](https://discord.gg/952SFk8Tb8).
+9 -9
View File
@@ -1,7 +1,7 @@
import type { AstroBkndConfig } from "bknd/adapter/astro";
import { registerLocalMediaAdapter } from "bknd/adapter/node";
import { boolean, em, entity, text } from "bknd";
import { secureRandomString } from "bknd/utils";
import type { AstroUserbaseConfig } from "userbase/adapter/astro";
import { registerLocalMediaAdapter } from "userbase/adapter/node";
import { boolean, em, entity, text } from "userbase";
import { secureRandomString } from "userbase/utils";
// since we're running in node, we can register the local media adapter
const local = registerLocalMediaAdapter();
@@ -16,7 +16,7 @@ const schema = em({
// register your schema to get automatic type completion
type Database = (typeof schema)["DB"];
declare module "bknd" {
declare module "userbase" {
interface DB extends Database {}
}
@@ -34,7 +34,7 @@ export default {
auth: {
enabled: true,
jwt: {
issuer: "bknd-astro-example",
issuer: "userbase-astro-example",
secret: secureRandomString(64),
},
},
@@ -51,15 +51,15 @@ export default {
seed: async (ctx) => {
// create some entries
await ctx.em.mutator("todos").insertMany([
{ title: "Learn bknd", done: true },
{ title: "Learn userbase", done: true },
{ title: "Build something cool", done: false },
]);
// and create a user
await ctx.app.module.auth.createUser({
email: "test@bknd.io",
email: "test@userbase.io",
password: "12345678",
});
},
},
} as const satisfies AstroBkndConfig;
} as const satisfies AstroUserbaseConfig;
+1 -1
View File
@@ -15,7 +15,7 @@
"@types/react": "^19.0.12",
"@types/react-dom": "^19.0.4",
"astro": "^5.16.9",
"bknd": "file:../../app",
"userbase": "file:../../app",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"typescript": "^5.7.2"
+3 -3
View File
@@ -1,11 +1,11 @@
import type { AstroGlobal } from "astro";
import { getApp as getBkndApp } from "bknd/adapter/astro";
import config from "../bknd.config";
import { getApp as getUserbaseApp } from "userbase/adapter/astro";
import config from "../userbase.config";
export { config };
export async function getApp() {
return await getBkndApp(config);
return await getUserbaseApp(config);
}
export async function getApi(
+1 -1
View File
@@ -31,7 +31,7 @@ const items = [
</nav>
<main>
<div class="center">
<h1>bknd + <span class="text-gradient">Astro</span></h1>
<h1>userbase + <span class="text-gradient">Astro</span></h1>
<slot name="context" />
</div>
@@ -1,8 +1,8 @@
---
import { Admin } from "bknd/ui";
import "bknd/dist/styles.css";
import { Admin } from "userbase/ui";
import "userbase/dist/styles.css";
import { getApi } from "../../bknd";
import { getApi } from "../../userbase";
const api = await getApi(Astro, { verify: true });
const user = api.getUser();
+2 -2
View File
@@ -1,6 +1,6 @@
import type { APIContext } from "astro";
import { serve } from "bknd/adapter/astro";
import { config } from "../../bknd";
import { serve } from "userbase/adapter/astro";
import { config } from "../../userbase";
export const prerender = false;
export const ALL = serve<APIContext>(config);
+1 -1
View File
@@ -1,5 +1,5 @@
---
import { getApi } from "../bknd";
import { getApi } from "../userbase";
import Card from "../components/Card.astro";
import Layout from "../layouts/Layout.astro";
+1 -1
View File
@@ -1,5 +1,5 @@
---
import { getApi } from "../bknd";
import { getApi } from "../userbase";
import Card from "../components/Card.astro";
import Layout from "../layouts/Layout.astro";
const api = await getApi(Astro, { verify: true });
+2 -2
View File
@@ -1,8 +1,8 @@
#!/bin/bash
# Set variables
FUNCTION_NAME="bknd-lambda"
ROLE_NAME="bknd-lambda-execution-role"
FUNCTION_NAME="userbase-lambda"
ROLE_NAME="userbase-lambda-execution-role"
echo "Starting cleanup of AWS resources..."
+3 -3
View File
@@ -1,8 +1,8 @@
#!/bin/bash
# Set variables
FUNCTION_NAME="bknd-lambda"
ROLE_NAME="bknd-lambda-execution-role"
FUNCTION_NAME="userbase-lambda"
ROLE_NAME="userbase-lambda-execution-role"
RUNTIME="nodejs22.x"
HANDLER="index.handler"
ARCHITECTURE="arm64" # or "x86_64"
@@ -16,7 +16,7 @@ echo "Building Lambda package..."
rm -rf dist && mkdir dist
# copy assets
node_modules/.bin/bknd copy-assets --out=dist/static
node_modules/.bin/userbase copy-assets --out=dist/static
# Run esbuild and check for errors
# important to use --platform=browser for libsql dependency (otherwise we need to push node_modules)
+2 -2
View File
@@ -1,7 +1,7 @@
import { serve } from "bknd/adapter/aws";
import { serve } from "userbase/adapter/aws";
export const handler = serve({
// to get local assets, run `npx bknd copy-assets`
// to get local assets, run `npx userbase copy-assets`
// this is automatically done in `deploy.sh`
assets: {
mode: "local",
+1 -1
View File
@@ -12,7 +12,7 @@
"license": "ISC",
"description": "",
"dependencies": {
"bknd": "file:../../app"
"userbase": "file:../../app"
},
"devDependencies": {
"esbuild": "^0.25.0",
+4 -4
View File
@@ -1,5 +1,5 @@
# bknd starter: Bun
A minimal Bun project with bknd integration.
# userbase starter: Bun
A minimal Bun project with userbase integration.
## Project Structure
@@ -12,7 +12,7 @@ Inside of your Bun project, you'll see the following folders and files:
└── package.json
```
To update `bknd` config, check `index.ts`.
To update `userbase` config, check `index.ts`.
## Commands
@@ -25,4 +25,4 @@ All commands are run from the root of the project, from a terminal:
## Want to learn more?
Feel free to check [our documentation](https://docs.bknd.io/integration/bun) or jump into our [Discord server](https://discord.gg/952SFk8Tb8).
Feel free to check [our documentation](https://docs.userbase.io/integration/bun) or jump into our [Discord server](https://discord.gg/952SFk8Tb8).
+2 -2
View File
@@ -1,10 +1,10 @@
import { type BunBkndConfig, serve } from "bknd/adapter/bun";
import { type BunUserbaseConfig, serve } from "userbase/adapter/bun";
// Actually, all it takes is the following line:
// serve();
// this is optional, if omitted, it uses an in-memory database
const config: BunBkndConfig = {
const config: BunUserbaseConfig = {
connection: {
url: "data.db",
},
+1 -1
View File
@@ -8,7 +8,7 @@
"start": "bun run index.ts"
},
"dependencies": {
"bknd": "file:../../app"
"userbase": "file:../../app"
},
"devDependencies": {
"@types/bun": "latest"
+40 -40
View File
@@ -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)
+3 -3
View File
@@ -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, {
+6 -6
View File
@@ -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(),
+1 -1
View File
@@ -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>
+7 -7
View File
@@ -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",
+4 -4
View File
@@ -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">&amp;</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 -1
View File
@@ -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"],
+39 -39
View File
@@ -1,19 +1,19 @@
# bknd starter: Cloudflare Vite Hybrid
A fullstack React + Vite application with bknd integration, showcasing **hybrid mode** and Cloudflare Workers deployment.
# 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 bknd 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 `bknd-config.json` and type definitions are generated in `bknd-types.d.ts`.
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 bknd's `devFsVitePlugin` and `devFsWrite` to provide filesystem access during development, enabling automatic syncing of types and configuration.
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
- **`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 # 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
├── 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
@@ -51,8 +51,8 @@ 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`.
- `/` showcases `useEntityQuery("todos")`, mutation helpers, and authentication state — demonstrating how the generated client types (`bknd-types.d.ts`) flow into the React code.
- `/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
@@ -60,25 +60,25 @@ Inside of your project, you'll see the following folders and 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`).
- 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 `bknd/ui`.
- Disables the built-in admin controller because the React app renders `/admin` via `userbase/ui`.
```typescript
import { hybrid } from "bknd/modes";
import { devFsWrite, type CloudflareBkndConfig } from "bknd/adapter/cloudflare";
import { hybrid } from "userbase/modes";
import { devFsWrite, type CloudflareUserbaseConfig } from "userbase/adapter/cloudflare";
export default hybrid<CloudflareBkndConfig>({
export default hybrid<CloudflareUserbaseConfig>({
// Special reader for Cloudflare Workers (no Node.js fs)
reader: async () => (await import("./bknd-config.json")).default,
reader: async () => (await import("./userbase-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",
typesFilePath: "./userbase-types.d.ts",
configFilePath: "./userbase-config.json",
syncSecrets: {
enabled: true,
outFile: ".env.example",
@@ -93,11 +93,11 @@ export default hybrid<CloudflareBkndConfig>({
});
```
### `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);
@@ -107,7 +107,7 @@ export default withPlatformProxy(config);
Includes the `devFsVitePlugin` for filesystem access:
```typescript
import { devFsVitePlugin } from "bknd/adapter/cloudflare";
import { devFsVitePlugin } from "userbase/adapter/cloudflare";
export default defineConfig({
plugins: [
@@ -129,8 +129,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:types` | Generates TypeScript types from your schema |
| `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 |
@@ -153,17 +153,17 @@ All commands are run from the root of the project, from a terminal:
- Define permissions
4. **Watch for auto-generated files:**
- `bknd-config.json` - Production configuration
- `bknd-types.d.ts` - TypeScript types
- `userbase-config.json` - Production configuration
- `userbase-types.d.ts` - TypeScript types
- `.env.example` - Required secrets
5. **Use the CLI** for manual operations:
```sh
# Generate types manually
npm run bknd:types
npm run userbase:types
# Sync the production database schema (only safe operations are applied)
CLOUDFLARE_ENV=production npm run bknd -- sync --force
CLOUDFLARE_ENV=production npm run userbase -- sync --force
```
## Before you deploy
@@ -200,8 +200,8 @@ This will:
2. Build the Vite application
3. Deploy to Cloudflare Workers using Wrangler
In production, bknd will:
- Use the configuration from `bknd-config.json` (read-only)
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
@@ -221,15 +221,15 @@ Check `.env.example` for all required secrets after running the app in developme
```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]
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 `bknd-config.json` and types to `bknd-types.d.ts`
3. **In Production:** `mode: "code"` - Configuration read from `bknd-config.json`, no database overhead
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?
@@ -242,8 +242,8 @@ The `devFsVitePlugin` + `devFsWrite` combination provides a workaround by using
## Want to learn more?
- [Cloudflare Integration Documentation](https://docs.bknd.io/integration/cloudflare)
- [Hybrid Mode Guide](https://docs.bknd.io/usage/introduction#hybrid-mode)
- [Mode Helpers Documentation](https://docs.bknd.io/usage/introduction#mode-helpers)
- [Cloudflare Integration Documentation](https://docs.userbase.io/integration/cloudflare)
- [Hybrid Mode Guide](https://docs.userbase.io/usage/introduction#hybrid-mode)
- [Mode Helpers Documentation](https://docs.userbase.io/usage/introduction#mode-helpers)
- [Discord Community](https://discord.gg/952SFk8Tb8)
@@ -158,7 +158,7 @@
"secret": "",
"alg": "HS256",
"expires": 0,
"issuer": "bknd-cloudflare-example",
"issuer": "userbase-cloudflare-example",
"fields": [
"id",
"email",
+5 -5
View File
@@ -1,10 +1,10 @@
import type { DB } from "bknd";
import type { DB } from "userbase";
import type { Insertable, Selectable, Updateable, Generated } from "kysely";
declare global {
type BkndEntity<T extends keyof DB> = Selectable<DB[T]>;
type BkndEntityCreate<T extends keyof DB> = Insertable<DB[T]>;
type BkndEntityUpdate<T extends keyof DB> = Updateable<DB[T]>;
type UserbaseEntity<T extends keyof DB> = Selectable<DB[T]>;
type UserbaseEntityCreate<T extends keyof DB> = Insertable<DB[T]>;
type UserbaseEntityUpdate<T extends keyof DB> = Updateable<DB[T]>;
}
export interface Todos {
@@ -17,6 +17,6 @@ interface Database {
todos: Todos;
}
declare module "bknd" {
declare module "userbase" {
interface DB extends Database {}
}
@@ -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, {
+9 -9
View File
@@ -1,19 +1,19 @@
/// <reference types="./worker-configuration.d.ts" />
import { devFsWrite, type CloudflareBkndConfig } from "bknd/adapter/cloudflare";
import { hybrid } from "bknd/modes";
import { devFsWrite, type CloudflareUserbaseConfig } from "userbase/adapter/cloudflare";
import { hybrid } from "userbase/modes";
export default hybrid<CloudflareBkndConfig>({
export default hybrid<CloudflareUserbaseConfig>({
// normally you would use e.g. `readFile` from `node:fs/promises`, however, cloudflare using vite plugin removes all Node APIs, therefore we need to use the module system to import the config file
reader: async () => {
return (await import("./bknd-config.json").then((module) => module.default)) as any;
return (await import("./userbase-config.json").then((module) => module.default)) as any;
},
// a writer is required to sync the types and config. We're using a vite plugin that proxies writing files (since Node APIs are not available)
writer: devFsWrite,
// the generated types are loaded using our tsconfig, and is automatically available in all bknd APIs
typesFilePath: "./bknd-types.d.ts",
// the generated types are loaded using our tsconfig, and is automatically available in all userbase APIs
typesFilePath: "./userbase-types.d.ts",
// on every change, this config file is updated. When it's time to deploy, this will be inlined into your worker
configFilePath: "./bknd-config.json",
configFilePath: "./userbase-config.json",
// secrets will always be extracted from the configuration, we're writing an example env file to know which secrets we need to provide prior to deploying
syncSecrets: {
enabled: true,
@@ -32,13 +32,13 @@ export default hybrid<CloudflareBkndConfig>({
seed: async (ctx) => {
// create some entries
await ctx.em.mutator("todos").insertMany([
{ title: "Learn bknd", done: true },
{ title: "Learn userbase", done: true },
{ title: "Build something cool", done: false },
]);
// and create a user
await ctx.app.module.auth.createUser({
email: "test@bknd.io",
email: "test@userbase.io",
password: "12345678",
});
},
+1 -1
View File
@@ -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>
+6 -6
View File
@@ -1,22 +1,22 @@
{
"name": "cloudflare-vite-fullstack-bknd",
"description": "A template for building a React application with Vite, Cloudflare Workers, and bknd",
"name": "cloudflare-vite-fullstack-userbase",
"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:types": "bknd -- types",
"userbase": "node --experimental-strip-types node_modules/.bin/userbase",
"userbase:types": "userbase -- types",
"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"
},
"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",
@@ -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">&amp;</div>
<div className="flex flex-row gap-2 items-center">
<img src={cloudflareLogo} alt="cloudflare" className="h-10" />
@@ -1,4 +1,4 @@
import { serve } from "bknd/adapter/cloudflare";
import { serve } from "userbase/adapter/cloudflare";
import config from "../../config.ts";
export default serve(config);
@@ -6,6 +6,6 @@
{ "path": "./tsconfig.worker.json" }
],
"compilerOptions": {
"types": ["./worker-configuration.d.ts", "./bknd-types.d.ts", "node"]
"types": ["./worker-configuration.d.ts", "./userbase-types.d.ts", "node"]
}
}
@@ -2,7 +2,7 @@ import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { cloudflare } from "@cloudflare/vite-plugin";
import tailwindcss from "@tailwindcss/vite";
import { devFsVitePlugin } from "bknd/adapter/cloudflare";
import { devFsVitePlugin } from "userbase/adapter/cloudflare";
export default defineConfig({
plugins: [
@@ -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"],
+1 -1
View File
@@ -167,5 +167,5 @@ dist
.dev.vars
.wrangler/
bknd-types.d.ts
userbase-types.d.ts
worker-configuration.d.ts
+4 -4
View File
@@ -1,5 +1,5 @@
# bknd starter: Cloudflare Workers
A minimal Node.js project with bknd integration.
# userbase starter: Cloudflare Workers
A minimal Node.js project with userbase integration.
## Project Structure
@@ -13,7 +13,7 @@ Inside of your Node.js project, you'll see the following folders and files:
└── wrangler.json
```
To update `bknd` config, check `src/index.ts`.
To update `userbase` config, check `src/index.ts`.
## Commands
@@ -34,4 +34,4 @@ npx wrangler d1 create my-database
## Want to learn more?
Feel free to check [our documentation](https://docs.bknd.io/integration/cloudflare) or jump into our [Discord server](https://discord.gg/952SFk8Tb8).
Feel free to check [our documentation](https://docs.userbase.io/integration/cloudflare) or jump into our [Discord server](https://discord.gg/952SFk8Tb8).
+1 -1
View File
@@ -5,7 +5,7 @@
* We're using separate files, so that "wrangler" doesn't get bundled with your worker.
*/
import { withPlatformProxy } from "bknd/adapter/cloudflare/proxy";
import { withPlatformProxy } from "userbase/adapter/cloudflare/proxy";
import config from "./config.ts";
export default withPlatformProxy(config);
+2 -2
View File
@@ -1,7 +1,7 @@
import type { CloudflareBkndConfig } from "bknd/adapter/cloudflare";
import type { CloudflareUserbaseConfig } from "userbase/adapter/cloudflare";
export default {
d1: {
session: true,
},
} satisfies CloudflareBkndConfig;
} satisfies CloudflareUserbaseConfig;
+3 -3
View File
@@ -6,12 +6,12 @@
"scripts": {
"deploy": "wrangler deploy",
"dev": "wrangler dev",
"bknd-typegen": "PROXY=1 npx bknd types",
"typegen": "wrangler types && npm run bknd-typegen",
"userbase-typegen": "PROXY=1 npx userbase types",
"typegen": "wrangler types && npm run userbase-typegen",
"predev": "npm run typegen"
},
"dependencies": {
"bknd": "file:../../app"
"userbase": "file:../../app"
},
"devDependencies": {
"typescript": "^5.9.2",
+1 -1
View File
@@ -1,4 +1,4 @@
import { serve } from "bknd/adapter/cloudflare";
import { serve } from "userbase/adapter/cloudflare";
import config from "../config";
export default serve(config);
+2 -2
View File
@@ -19,8 +19,8 @@
"exclude": ["test"],
"include": [
"worker-configuration.d.ts",
"bknd-types.d.ts",
"bknd.config.ts",
"userbase-types.d.ts",
"userbase.config.ts",
"src/**/*.ts"
]
}
+3 -3
View File
@@ -1,6 +1,6 @@
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "bknd-cf-worker-example",
"name": "userbase-cf-worker-example",
"main": "src/index.ts",
"compatibility_date": "2025-08-03",
"compatibility_flags": ["nodejs_compat"],
@@ -15,14 +15,14 @@
"d1_databases": [
{
"binding": "DB",
"database_name": "bknd-dev-weur",
"database_name": "userbase-dev-weur",
"database_id": "81d8dfcc-4eaf-4453-8f0f-8f6d463fb867"
}
],
"r2_buckets": [
{
"binding": "BUCKET",
"bucket_name": "bknd-cf-example"
"bucket_name": "userbase-cf-example"
}
]
}
+1 -1
View File
@@ -4,7 +4,7 @@
"dev": "deno serve -A --watch main.ts"
},
"imports": {
"bknd": "npm:bknd@0.19.0-rc.1"
"userbase": "npm:userbase@0.19.0-rc.1"
},
"links": ["../../app/"],
"unstable": ["raw-imports"]
+1 -1
View File
@@ -1,4 +1,4 @@
import { createRuntimeApp, serveStaticViaImport } from "bknd/adapter";
import { createRuntimeApp, serveStaticViaImport } from "userbase/adapter";
const app = await createRuntimeApp({
connection: {
+8 -8
View File
@@ -1,5 +1,5 @@
# bknd starter: Next.js
A minimal Next.js (app router) project with bknd integration.
# userbase starter: Next.js
A minimal Next.js (app router) project with userbase integration.
## Project Structure
@@ -17,16 +17,16 @@ Inside of your Next.js project, you'll see the following folders and files:
│ │ │ └── [[...admin]]
│ │ │ └── page.tsx
│ │ ├── api
│ │ │ └── [[...bknd]]
│ │ │ └── [[...userbase]]
│ │ │ └── route.ts
│ │ └── ...
│ └── bknd.ts
│ └── userbase.ts
└── package.json
```
Here is a quick overview about how to adjust the behavior of `bknd`:
* Initialization of the `bknd` config with helper functions are located at `src/bknd.ts`
* API routes are exposed at `src/api/[[...bknd]]/route.ts`
Here is a quick overview about how to adjust the behavior of `userbase`:
* Initialization of the `userbase` config with helper functions are located at `src/userbase.ts`
* API routes are exposed at `src/api/[[...userbase]]/route.ts`
* Admin UI is rendered at `src/admin/[[...admin]]/page.tsx`
## Commands
@@ -42,4 +42,4 @@ All commands are run from the root of the project, from a terminal:
## Want to learn more?
Feel free to check [our documentation](https://docs.bknd.io/integration/nextjs) or jump into our [Discord server](https://discord.gg/952SFk8Tb8).
Feel free to check [our documentation](https://docs.userbase.io/integration/nextjs) or jump into our [Discord server](https://discord.gg/952SFk8Tb8).
+10 -10
View File
@@ -1,13 +1,13 @@
import type { NextjsBkndConfig } from "bknd/adapter/nextjs";
import { boolean, em, entity, text } from "bknd";
import { registerLocalMediaAdapter } from "bknd/adapter/node";
import { secureRandomString } from "bknd/utils";
import type { NextjsUserbaseConfig } from "userbase/adapter/nextjs";
import { boolean, em, entity, text } from "userbase";
import { registerLocalMediaAdapter } from "userbase/adapter/node";
import { secureRandomString } from "userbase/utils";
// The local media adapter works well in development, and server based
// deployments. However, on vercel or any other serverless deployments,
// you shouldn't use a filesystem based media adapter.
//
// Additionally, if you run the bknd api on the "edge" runtime,
// Additionally, if you run the userbase api on the "edge" runtime,
// this would not work as well.
//
// For production, it is recommended to uncomment the line below.
@@ -22,7 +22,7 @@ const schema = em({
// register your schema to get automatic type completion
type Database = (typeof schema)["DB"];
declare module "bknd" {
declare module "userbase" {
interface DB extends Database {}
}
@@ -39,7 +39,7 @@ export default {
auth: {
enabled: true,
jwt: {
issuer: "bknd-nextjs-example",
issuer: "userbase-nextjs-example",
secret: secureRandomString(64),
},
cookie: {
@@ -60,15 +60,15 @@ export default {
seed: async (ctx) => {
// create some entries
await ctx.em.mutator("todos").insertMany([
{ title: "Learn bknd", done: true },
{ title: "Learn userbase", done: true },
{ title: "Build something cool", done: false },
]);
// and create a user
await ctx.app.module.auth.createUser({
email: "test@bknd.io",
email: "test@userbase.io",
password: "12345678",
});
},
},
} as const satisfies NextjsBkndConfig;
} as const satisfies NextjsUserbaseConfig;
+1 -1
View File
@@ -10,7 +10,7 @@
"lint": "next lint"
},
"dependencies": {
"bknd": "file:../../app",
"userbase": "file:../../app",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"next": "15.2.0"
+2 -2
View File
@@ -21,8 +21,8 @@ export default async function Layout({
<div className="ml-3.5 mr-2 font-mono opacity-70">&amp;</div>
<Image
className="dark:invert"
src="/bknd.svg"
alt="bknd logo"
src="/userbase.svg"
alt="userbase logo"
width={183}
height={59}
priority
+1 -1
View File
@@ -1,4 +1,4 @@
import { getApi } from "@/bknd";
import { getApi } from "@/userbase";
import { revalidatePath } from "next/cache";
import { Fragment } from "react";
import { List } from "@/components/List";
+2 -2
View File
@@ -1,4 +1,4 @@
import { getApi } from "@/bknd";
import { getApi } from "@/userbase";
import { Buttons } from "@/components/Buttons";
import { List } from "@/components/List";
import Link from "next/link";
@@ -32,7 +32,7 @@ export default async function SSRPage() {
<p className="text-xs opacity-50">
Sign in with:{" "}
<b>
<code>test@bknd.io</code>
<code>test@userbase.io</code>
</b>{" "}
/{" "}
<b>
@@ -1,9 +1,9 @@
"use client";
import { type BkndAdminProps, Admin } from "bknd/ui";
import { type UserbaseAdminProps, Admin } from "userbase/ui";
import { useEffect, useState } from "react";
export function AdminComponent(props: BkndAdminProps) {
export function AdminComponent(props: UserbaseAdminProps) {
const [ready, setReady] = useState(false);
useEffect(() => {
@@ -1,6 +1,6 @@
import { AdminComponent } from "./Admin";
import { getApi } from "@/bknd";
import "bknd/dist/styles.css";
import { getApi } from "@/userbase";
import "userbase/dist/styles.css";
export default async function AdminPage() {
const api = await getApi({ verify: true });
@@ -1,5 +1,5 @@
import { config } from "@/bknd";
import { serve } from "bknd/adapter/nextjs";
import { config } from "@/userbase";
import { serve } from "userbase/adapter/nextjs";
// since we're using the local media adapter in this example,
// we can't use the edge runtime.
@@ -8,7 +8,7 @@ import { serve } from "bknd/adapter/nextjs";
const handler = serve({
...config,
cleanRequest: {
searchParams: ["bknd"],
searchParams: ["userbase"],
},
});
+2 -2
View File
@@ -13,8 +13,8 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
title: "Create Next & bknd App",
description: "Presented by create next app & bknd",
title: "Create Next & userbase App",
description: "Presented by create next app & userbase",
};
export default function RootLayout({
+3 -3
View File
@@ -1,11 +1,11 @@
import { getApp as getBkndApp } from "bknd/adapter/nextjs";
import { getApp as getUserbaseApp } from "userbase/adapter/nextjs";
import { headers } from "next/headers";
import config from "../bknd.config";
import config from "../userbase.config";
export { config };
export async function getApp() {
return await getBkndApp(config, process.env);
return await getUserbaseApp(config, process.env);
}
export async function getApi(opts?: { verify?: boolean }) {
+1 -1
View File
@@ -19,7 +19,7 @@ export const Buttons = () => (
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
href="https://docs.bknd.io/integration/nextjs"
href="https://docs.userbase.io/integration/nextjs"
target="_blank"
rel="noopener noreferrer"
>
+2 -2
View File
@@ -25,12 +25,12 @@ export function Footer() {
</Link>
<Link
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://bknd.io"
href="https://userbase.io"
target="_blank"
rel="noopener noreferrer"
>
<Image aria-hidden src="/globe.svg" alt="Globe icon" width={16} height={16} />
Go to bknd.io →
Go to userbase.io →
</Link>
</footer>
);
+4 -4
View File
@@ -1,5 +1,5 @@
# bknd starter: Node.js
A minimal Node.js project with bknd integration.
# userbase starter: Node.js
A minimal Node.js project with userbase integration.
## Project Structure
@@ -11,7 +11,7 @@ Inside of your Node.js project, you'll see the following folders and files:
└── package.json
```
To update `bknd` config, check `index.js`.
To update `userbase` config, check `index.js`.
## Commands
@@ -25,4 +25,4 @@ All commands are run from the root of the project, from a terminal:
## Want to learn more?
Feel free to check [our documentation](https://docs.bknd.io/integration/node) or jump into our [Discord server](https://discord.gg/952SFk8Tb8).
Feel free to check [our documentation](https://docs.userbase.io/integration/node) or jump into our [Discord server](https://discord.gg/952SFk8Tb8).
+2 -2
View File
@@ -1,10 +1,10 @@
import { serve } from "bknd/adapter/node";
import { serve } from "userbase/adapter/node";
// Actually, all it takes is the following line:
// serve();
// this is optional, if omitted, it uses an in-memory database
/** @type {import("bknd/adapter/node").NodeBkndConfig} */
/** @type {import("userbase/adapter/node").NodeUserbaseConfig} */
const config = {
connection: {
url: "data.db",
+1 -1
View File
@@ -8,7 +8,7 @@
"dev": "tsx --watch index.js"
},
"dependencies": {
"bknd": "file:../../app",
"userbase": "file:../../app",
"@hono/node-server": "^1.13.3"
},
"devDependencies": {
+12 -12
View File
@@ -1,5 +1,5 @@
# bknd starter: Nuxt
A minimal example of a Nuxt project with bknd integration.
# userbase starter: Nuxt
A minimal example of a Nuxt project with userbase integration.
## Project Structure
@@ -20,14 +20,14 @@ Inside of your Nuxt project, you'll see the following folders and files:
│ └── pages
│ ├── index.vue
│ └── user.vue
├── bknd.config.ts
├── userbase.config.ts
├── bun.lock
├── nuxt.config.ts
├── package.json
├── public
│ ├── admin # generated by bknd and contains the admin UI
│ ├── bknd.ico
│ ├── bknd.svg
│ ├── admin # generated by userbase and contains the admin UI
│ ├── userbase.ico
│ ├── userbase.svg
│ ├── favicon.ico
│ ├── file.svg
│ ├── globe.svg
@@ -37,17 +37,17 @@ Inside of your Nuxt project, you'll see the following folders and files:
├── README.md
├── server
│ ├── middleware
│ │ └── bknd.ts # intercepts api and admin ui requests
│ │ └── userbase.ts # intercepts api and admin ui requests
│ ├── routes
│ │ └── todos.post.ts
│ └── utils
│ └── bknd.ts # initializes bknd instance
│ └── userbase.ts # initializes userbase instance
└── tsconfig.json
```
Here is a quick overview about how to adjust the behavior of `bknd`:
* Initialization of the `bknd` config with helper functions are located at `src/server/utils/bknd.ts`
* Admin UI is rendered at `src/server/middleware/bknd.ts`
Here is a quick overview about how to adjust the behavior of `userbase`:
* Initialization of the `userbase` config with helper functions are located at `src/server/utils/userbase.ts`
* Admin UI is rendered at `src/server/middleware/userbase.ts`
## Commands
@@ -61,4 +61,4 @@ All commands are run from the root of the project, from a terminal:
## Want to learn more?
Feel free to check [our documentation](https://docs.bknd.io/integration/nuxt) or jump into our [Discord server](https://discord.gg/952SFk8Tb8).
Feel free to check [our documentation](https://docs.userbase.io/integration/nuxt) or jump into our [Discord server](https://discord.gg/952SFk8Tb8).
+4 -4
View File
@@ -1,12 +1,12 @@
<template>
<div className="flex gap-4 items-center flex-col sm:flex-row">
<a className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground gap-2 text-white hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
href="https://bknd.io/" target="_blank" rel="noopener noreferrer">
<img className="grayscale" src="/bknd.ico" alt="bknd logomark" width={20} height={20} />
Go To Bknd.io
href="https://userbase.io/" target="_blank" rel="noopener noreferrer">
<img className="grayscale" src="/userbase.ico" alt="userbase logomark" width={20} height={20} />
Go To Userbase.io
</a>
<a className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
href="https://docs.bknd.io/integration/nextjs" target="_blank" rel="noopener noreferrer">
href="https://docs.userbase.io/integration/nextjs" target="_blank" rel="noopener noreferrer">
Read our docs
</a>
</div>
+2 -2
View File
@@ -20,10 +20,10 @@ const pathname = computed(() => route.path)
Admin
</NuxtLink>
<a class="flex items-center gap-2 hover:underline hover:underline-offset-4" href="https://bknd.io" target="_blank"
<a class="flex items-center gap-2 hover:underline hover:underline-offset-4" href="https://userbase.io" target="_blank"
rel="noopener noreferrer">
<img aria-hidden src="/globe.svg" alt="Globe icon" width="16" height="16" />
Go to bknd.io →
Go to userbase.io →
</a>
</footer>
</template>
@@ -1,4 +1,4 @@
import type { DB } from "bknd";
import type { DB } from "userbase";
type Todo = DB["todos"];
+1 -1
View File
@@ -1,4 +1,4 @@
import type { User } from "bknd";
import type { User } from "userbase";
export const useUser = () => {
const getUser = () => $fetch("/api/auth/me") as Promise<{ user: User }>;
+1 -1
View File
@@ -24,7 +24,7 @@ async function handleSubmit(event: Event) {
<div class="flex flex-row items-center justify-evenly min-w-full">
<img class="size-24" src="/nuxt.svg" alt="Nuxt logo" />
<div class="ml-3.5 mr-2 font-mono opacity-70">&amp;</div>
<img class="dark:invert" src="/bknd.svg" alt="bknd logo" width="183" height="59" />
<img class="dark:invert" src="/userbase.svg" alt="userbase logo" width="183" height="59" />
</div>
<List :items="['Get started with a full backend.', 'Focus on what matters instead of repetition.']" />
+3 -3
View File
@@ -19,8 +19,8 @@ onMounted(() => {
<div class="ml-3.5 mr-2 font-mono opacity-70">&amp;</div>
<img
class="dark:invert"
src="/bknd.svg"
alt="bknd logo"
src="/userbase.svg"
alt="userbase logo"
width="183"
height="59"
/>
@@ -41,7 +41,7 @@ onMounted(() => {
<p className="text-xs opacity-50">
Sign in with:
<b>
<code>test@bknd.io</code>
<code>test@userbase.io</code>
</b>
/
<b>
+8 -8
View File
@@ -1,7 +1,7 @@
import { em, entity, text, boolean, } from "bknd";
import { secureRandomString } from "bknd/utils";
import type { NuxtBkndConfig } from "bknd/adapter/nuxt";
import { registerLocalMediaAdapter } from "bknd/adapter/node";
import { em, entity, text, boolean, } from "userbase";
import { secureRandomString } from "userbase/utils";
import type { NuxtUserbaseConfig } from "userbase/adapter/nuxt";
import { registerLocalMediaAdapter } from "userbase/adapter/node";
const local = registerLocalMediaAdapter();
@@ -14,7 +14,7 @@ const schema = em({
// register your schema to get automatic type completion
type Database = (typeof schema)["DB"];
declare module "bknd" {
declare module "userbase" {
interface DB extends Database { }
}
@@ -25,13 +25,13 @@ export default {
seed: async (ctx) => {
// create some entries
await ctx.em.mutator("todos").insertMany([
{ title: "Learn bknd", done: true },
{ title: "Learn userbase", done: true },
{ title: "Build something cool", done: false },
]);
// and create a user
await ctx.app.module.auth.createUser({
email: "test@bknd.io",
email: "test@userbase.io",
password: "12345678",
});
},
@@ -56,4 +56,4 @@ export default {
assetsPath: "/admin/",
logoReturnPath: "../..",
},
} satisfies NuxtBkndConfig;
} satisfies NuxtUserbaseConfig;
+1 -1
View File
@@ -5,7 +5,7 @@ export default defineNuxtConfig({
modules: ["@nuxtjs/tailwindcss"],
app: {
head: {
title: "Nuxt 🤝 Bknd.io",
title: "Nuxt 🤝 Userbase.io",
},
},
css: ["assets/css/main.css"],
+4 -4
View File
@@ -1,5 +1,5 @@
{
"name": "nuxbknd",
"name": "nuxuserbase",
"type": "module",
"private": true,
"scripts": {
@@ -7,13 +7,13 @@
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"typegen": "bunx tsx node_modules/.bin/bknd types --outfile bknd-types.d.ts",
"postinstall": "nuxt prepare && bun run bknd copy-assets --out public/admin"
"typegen": "bunx tsx node_modules/.bin/userbase types --outfile userbase-types.d.ts",
"postinstall": "nuxt prepare && bun run userbase copy-assets --out public/admin"
},
"dependencies": {
"@nuxtjs/tailwindcss": "6.14.0",
"@types/node": "^25.2.3",
"bknd": "file:../../app",
"userbase": "file:../../app",
"nuxt": "^4.3.1",
"vue": "^3.5.28",
"vue-router": "^4.6.4"
+2 -2
View File
@@ -1,5 +1,5 @@
import { serve } from "bknd/adapter/nuxt";
import config from "../../bknd.config";
import { serve } from "userbase/adapter/nuxt";
import config from "../../userbase.config";
export default defineEventHandler(async (event) => {
const pathname = event.path
+4 -4
View File
@@ -1,15 +1,15 @@
import { type NuxtBkndConfig, getApp as getNuxtApp } from "bknd/adapter/nuxt";
import bkndConfig from "../../bknd.config";
import { type NuxtUserbaseConfig, getApp as getNuxtApp } from "userbase/adapter/nuxt";
import userbaseConfig from "../../userbase.config";
export async function getApp<Env = NodeJS.ProcessEnv>(
config: NuxtBkndConfig<Env>,
config: NuxtUserbaseConfig<Env>,
args: Env = process.env as Env,
) {
return await getNuxtApp(config, args);
}
export async function getApi({ headers, verify }: { verify?: boolean; headers?: Headers }) {
const app = await getApp(bkndConfig, process.env);
const app = await getApp(userbaseConfig, process.env);
if (verify) {
const api = app.getApi({ headers });
+2 -2
View File
@@ -9,9 +9,9 @@
"preview": "vite preview"
},
"dependencies": {
"@bknd/plasmic": "workspace:*",
"@userbase/plasmic": "workspace:*",
"@plasmicapp/loader-react": "^1.0.373",
"bknd": "workspace:*"
"userbase": "workspace:*"
},
"devDependencies": {}
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { registerAll } from "@bknd/plasmic";
import { registerAll } from "@userbase/plasmic";
import {
type ComponentRenderData,
PlasmicCanvasHost,
+2 -2
View File
@@ -1,7 +1,7 @@
/// <reference types="vite/client" />
import { Admin } from "bknd/ui";
import "bknd/dist/styles.css";
import { Admin } from "userbase/ui";
import "userbase/dist/styles.css";
export default function AdminPage() {
return <Admin config={{ basepath: "/admin" }} />;
+1 -1
View File
@@ -1,4 +1,4 @@
import { ClientProvider } from "bknd/client";
import { ClientProvider } from "userbase/client";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
+5 -5
View File
@@ -1,6 +1,6 @@
import { serve } from "bknd/adapter/vite";
import { App, boolean, em, entity, text } from "bknd";
import { secureRandomString } from "bknd/utils";
import { serve } from "userbase/adapter/vite";
import { App, boolean, em, entity, text } from "userbase";
import { secureRandomString } from "userbase/utils";
export default serve({
config: {
@@ -20,7 +20,7 @@ export default serve({
options: {
seed: async (ctx) => {
await ctx.em.mutator("todos" as any).insertMany([
{ title: "Learn bknd", done: true },
{ title: "Learn userbase", done: true },
{ title: "Build something cool", done: false },
]);
},
@@ -32,7 +32,7 @@ export default serve({
async () => {
// ... to create an initial user
await app.module.auth.createUser({
email: "ds@bknd.io",
email: "ds@userbase.io",
password: "12345678",
});
},
+1 -1
View File
@@ -1,4 +1,4 @@
import { devServer } from "bknd/adapter/vite";
import { devServer } from "userbase/adapter/vite";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
+3 -3
View File
@@ -1,5 +1,5 @@
import { serve } from "bknd/adapter/bun";
import { createCustomPostgresConnection } from "bknd";
import { serve } from "userbase/adapter/bun";
import { createCustomPostgresConnection } from "userbase";
import { NeonDialect } from "kysely-neon";
const neon = createCustomPostgresConnection("neon", NeonDialect);
@@ -9,6 +9,6 @@ export default serve({
connectionString: process.env.NEON,
}),
// ignore this, it's only required within this repository
// because bknd is installed via "workspace:*"
// because userbase is installed via "workspace:*"
distPath: "../../app/dist",
});
+1 -1
View File
@@ -9,7 +9,7 @@
"@xata.io/client": "^0.0.0-next.v93343b9646f57a1e5c51c35eccf0767c2bb80baa",
"@xata.io/kysely": "^0.2.1",
"kysely-neon": "^1.3.0",
"bknd": "file:../app",
"userbase": "file:../app",
"kysely": "0.27.6"
},
"devDependencies": {
+2 -2
View File
@@ -24,8 +24,8 @@
"esModuleInterop": true,
"skipLibCheck": true,
"paths": {
"bknd": ["../app/src/index.ts"],
"bknd/*": ["../app/src/*"]
"userbase": ["../app/src/index.ts"],
"userbase/*": ["../app/src/*"]
}
},
"include": ["./src/**/*.ts"],
+3 -3
View File
@@ -1,5 +1,5 @@
import { serve } from "bknd/adapter/bun";
import { createCustomPostgresConnection } from "bknd";
import { serve } from "userbase/adapter/bun";
import { createCustomPostgresConnection } from "userbase";
import { XataDialect } from "@xata.io/kysely";
import { buildClient } from "@xata.io/client";
@@ -19,6 +19,6 @@ const xata = createCustomPostgresConnection("xata", XataDialect, {
export default serve({
connection: xata(xataClient),
// ignore this, it's only required within this repository
// because bknd is installed via "workspace:*"
// because userbase is installed via "workspace:*"
distPath: "../../../app/dist",
});
+5 -5
View File
@@ -1,5 +1,5 @@
# bknd starter: React Router
A minimal React Router project with bknd integration.
# userbase starter: React Router
A minimal React Router project with userbase integration.
## Project Structure
@@ -9,7 +9,7 @@ Inside of your React Router project, you'll see the following folders and files:
/
├── public/
├── app/
│ ├── bknd.ts
│ ├── userbase.ts
│ ├── root.tsx
│ └── routes/
│ ├── _index.tsx
@@ -18,7 +18,7 @@ Inside of your React Router project, you'll see the following folders and files:
└── package.json
```
To update `bknd` config, check `app/routes/api.$.ts` and `app/routes/admin.$.ts`.
To update `userbase` config, check `app/routes/api.$.ts` and `app/routes/admin.$.ts`.
## Commands
@@ -32,4 +32,4 @@ All commands are run from the root of the project, from a terminal:
## Want to learn more?
Feel free to check [our documentation](https://docs.bknd.io/integration/remix) or jump into our [Discord server](https://discord.gg/952SFk8Tb8).
Feel free to check [our documentation](https://docs.userbase.io/integration/remix) or jump into our [Discord server](https://discord.gg/952SFk8Tb8).
+3 -3
View File
@@ -1,8 +1,8 @@
import { getApp as getBkndApp } from "bknd/adapter/react-router";
import config from "../bknd.config";
import { getApp as getUserbaseApp } from "userbase/adapter/react-router";
import config from "../userbase.config";
export async function getApp() {
return await getBkndApp(config, process.env as any);
return await getUserbaseApp(config, process.env as any);
}
export async function getApi(args?: { request: Request }, opts?: { verify?: boolean }) {
+4 -4
View File
@@ -5,13 +5,13 @@ import {
useFetcher,
useLoaderData,
} from "react-router";
import { getApi } from "~/bknd";
import { getApi } from "~/userbase";
// biome-ignore lint/correctness/noEmptyPattern: <explanation>
export function meta({}: Route.MetaArgs) {
return [
{ title: "New bknd React Router App" },
{ name: "description", content: "Welcome to bknd & React Router!" },
{ title: "New userbase React Router App" },
{ name: "description", content: "Welcome to userbase & React Router!" },
];
}
@@ -38,7 +38,7 @@ export default function Index() {
<div className="flex h-screen items-center justify-center">
<div className="flex flex-col items-center gap-16">
<header className="flex flex-col items-center gap-9">
<img src="/bknd.svg" alt="bknd" className="block w-48 dark:invert" />
<img src="/userbase.svg" alt="userbase" className="block w-48 dark:invert" />
<div className="h-[144px] w-96">
<img
src="/logo-light.svg"
+3 -3
View File
@@ -1,9 +1,9 @@
import { lazy, Suspense, useSyncExternalStore } from "react";
import { type LoaderFunctionArgs, useLoaderData } from "react-router";
import { getApi } from "~/bknd";
import { getApi } from "~/userbase";
const Admin = lazy(() => import("bknd/ui").then((mod) => ({ default: mod.Admin })));
import "bknd/dist/styles.css";
const Admin = lazy(() => import("userbase/ui").then((mod) => ({ default: mod.Admin })));
import "userbase/dist/styles.css";
export const loader = async (args: LoaderFunctionArgs) => {
const api = await getApi(args, { verify: true });
+1 -1
View File
@@ -1,4 +1,4 @@
import { getApp } from "~/bknd";
import { getApp } from "~/userbase";
const handler = async (args: { request: Request }) => {
const app = await getApp();
+9 -9
View File
@@ -1,7 +1,7 @@
import { registerLocalMediaAdapter } from "bknd/adapter/node";
import type { ReactRouterBkndConfig } from "bknd/adapter/react-router";
import { boolean, em, entity, text } from "bknd";
import { secureRandomString } from "bknd/utils";
import { registerLocalMediaAdapter } from "userbase/adapter/node";
import type { ReactRouterUserbaseConfig } from "userbase/adapter/react-router";
import { boolean, em, entity, text } from "userbase";
import { secureRandomString } from "userbase/utils";
// since we're running in node, we can register the local media adapter
const local = registerLocalMediaAdapter();
@@ -15,7 +15,7 @@ const schema = em({
// register your schema to get automatic type completion
type Database = (typeof schema)["DB"];
declare module "bknd" {
declare module "userbase" {
interface DB extends Database {}
}
@@ -33,7 +33,7 @@ export default {
auth: {
enabled: true,
jwt: {
issuer: "bknd-remix-example",
issuer: "userbase-remix-example",
secret: secureRandomString(64),
},
},
@@ -50,15 +50,15 @@ export default {
seed: async (ctx) => {
// create some entries
await ctx.em.mutator("todos").insertMany([
{ title: "Learn bknd", done: true },
{ title: "Learn userbase", done: true },
{ title: "Build something cool", done: false },
]);
// and create a user
await ctx.app.module.auth.createUser({
email: "test@bknd.io",
email: "test@userbase.io",
password: "12345678",
});
},
},
} as const satisfies ReactRouterBkndConfig<{ DB_URL?: string }>;
} as const satisfies ReactRouterUserbaseConfig<{ DB_URL?: string }>;
+1 -1
View File
@@ -12,7 +12,7 @@
"@react-router/fs-routes": "^7.3.0",
"@react-router/node": "^7.3.0",
"@react-router/serve": "^7.3.0",
"bknd": "file:../../app",
"userbase": "file:../../app",
"isbot": "^5.1.17",
"react": "^19.0.0",
"react-dom": "^19.0.0",
+1 -1
View File
@@ -1 +1 @@
# local bknd POC (WIP)
# local userbase POC (WIP)
+1 -1
View File
@@ -10,7 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"bknd": "file:../../app",
"userbase": "file:../../app",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"sqlocal": "^0.16.0",

Some files were not shown because too many files have changed in this diff Show More