Merge pull request #348 from bknd-io/intro-docs-updates

Docs: consolidate dual introductions, restructure setup page
This commit is contained in:
Cameron Pak
2026-02-07 17:23:21 -06:00
committed by GitHub
10 changed files with 223 additions and 298 deletions

View File

@@ -16,7 +16,7 @@
},
"app": {
"name": "bknd",
"version": "0.20.0-rc.1",
"version": "0.20.0",
"bin": "./dist/cli/index.js",
"dependencies": {
"@cfworker/json-schema": "^4.1.1",
@@ -3846,7 +3846,7 @@
"@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
"@bknd/plasmic/@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="],
"@bknd/plasmic/@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
"@bundled-es-modules/tough-cookie/tough-cookie": ["tough-cookie@4.1.4", "", { "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" } }, "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag=="],
@@ -4750,7 +4750,7 @@
"@babel/preset-env/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.3", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", "resolve": "^1.14.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg=="],
"@bknd/plasmic/@types/bun/bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="],
"@bknd/plasmic/@types/bun/bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
"@bundled-es-modules/tough-cookie/tough-cookie/universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="],
@@ -5236,8 +5236,6 @@
"@babel/preset-env/babel-plugin-polyfill-regenerator/@babel/helper-define-polyfill-provider/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
"@bknd/plasmic/@types/bun/bun-types/@types/node": ["@types/node@24.10.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A=="],
"@cloudflare/vitest-pool-workers/miniflare/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20251011.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-0DirVP+Z82RtZLlK2B+VhLOkk+ShBqDYO/jhcRw4oVlp0TOvk3cOVZChrt3+y3NV8Y/PYgTEywzLKFSziK4wCg=="],
"@cloudflare/vitest-pool-workers/miniflare/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20251011.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-1WuFBGwZd15p4xssGN/48OE2oqokIuc51YvHvyNivyV8IYnAs3G9bJNGWth1X7iMDPe4g44pZrKhRnISS2+5dA=="],

View File

@@ -7,7 +7,7 @@ import { TypeTable } from "fumadocs-ui/components/type-table";
bknd features an integrated Admin UI that can be used to:
- fully manage your backend visually when run in [`db` mode](/usage/introduction/#ui-only-mode)
- fully manage your backend visually when run in [`db` mode](/usage/setup/#ui-only-mode)
- manage your database contents
- manage your media contents

View File

@@ -121,7 +121,7 @@ If the connection object is omitted, the app will try to use an in-memory databa
As configuration, you can either pass a partial configuration object or a complete one
with a version number. The version number is used to automatically migrate the configuration up
to the latest version upon boot ([`db` mode](/usage/introduction#ui-only-mode) only). The default configuration looks like this:
to the latest version upon boot ([`db` mode](/usage/setup#ui-only-mode) only). The default configuration looks like this:
```json
{

View File

@@ -9,7 +9,7 @@
"start",
"motivation",
"---Usage---",
"./usage/introduction",
"./usage/setup",
"./usage/database",
"./usage/cli",
"./usage/sdk",

View File

@@ -6,26 +6,36 @@ tags: ["documentation"]
import { Icon } from "@iconify/react";
import { examples } from "@/app/_components/StackBlitz";
import { SquareMousePointer, Code, Blend } from 'lucide-react';
import { SquareMousePointer, Code, Blend, Rocket } from 'lucide-react';
<Callout type="warning" title="bknd is in beta">
## bknd is a lightweight batteries-included backend that embeds into your frontend app
<Callout type="warning" title="We are in beta">
We're making great progress towards v1, but don't recommend production use yet.
Follow along for updates on [GitHub Releases](https://github.com/bknd-io/bknd/releases) and in our [Discord community](https://discord.gg/952SFk8Tb8)
</Callout>
Welcome! bknd is the instant backend for your frontend with full REST API's, admin dashboard, auth and user management, file upload management, [type-safe SDK](/usage/sdk), [React hooks](/usage/react), and plugins to extend the bknd _(like our resend plugin for sending emails)_. bknd can be hosted with your server-side rendered (SSR) web app or hosted as a standalone app.
bknd is incredibly lightweight and built upon Web Standards, so that you can bring bknd anywhere JavaScript runs.
bknd includes full REST APIs, an admin dashboard, auth, media uploads, a [type-safe SDK](/usage/sdk), [React hooks](/usage/react), and plugins to extend it. Host it with your SSR app or as a standalone service. Built on Web Standards, it runs anywhere JavaScript runs.
Bring your [favorite frontend](./#start-with-a-frameworkruntime) and [favorite SQL database](./#use-your-favorite-sql-database), and we'll bring the ~~backend~~ bknd.
## Quickstart: bknd server and admin dashboard demo
<Cards>
<Card
href="/motivation"
title="Learn about why we built bknd"
icon={<Rocket />}
>
Why another backend system?
</Card>
</Cards>
## Quickstart
<Callout type="info" title="This demo bknd instance is for playing and learning">
Don't worry about messing anything up in this stage since you're learning the ropes of bknd. If you want to start over, please delete the generated `data.db` database file and follow this tutorial again.
</Callout>
Enter the following command to spin up a bknd instance via the [bknd CLI](/usage/cli):
Spin up a bknd instance via the [bknd CLI](/usage/cli):
<Tabs groupId='package-manager' persist items={[ 'npm','bun' ]}>
@@ -39,11 +49,9 @@ bunx bknd run
</Tabs>
This will create a local `data.db` SQLite database file in the folder you ran the command and start the bknd web server at http://localhost:1337.
This creates a local `data.db` SQLite database and starts the bknd web server at http://localhost:1337.
By default, access to the admin dashboard is open and not guarded. This is intentional because bknd uses an opt-in philosophy model to allow for quick prototyping and intentional configuration. To restrict access and prevent unauthorized use, let's enable authentication and guard the dashboard.
We will create a user and an admin role. We will then apply the admin role to the user. Then, we can lock down the admin dashboard by enabling the guard, securing access to your bknd app. Let's begin.
By default, the admin dashboard is open and not guarded. This is intentional bknd uses an opt-in philosophy to allow quick prototyping. Let's enable authentication and guard the dashboard to secure it.
1. Visit http://localhost:1337/auth/settings. Toggle "Authentication Enabled" to enable auth. Select "Update" to save.
2. Visit http://localhost:1337/data/entity/users. Create a user by selecting "New User" and entering an email and password.
@@ -52,29 +60,27 @@ We will create a user and an admin role. We will then apply the admin role to th
5. It's time to guard your admin dashboard. Visit http://localhost:1337/settings/auth. Select "Edit". Scroll to "Guard" and enable it. Then, select "Save". _(This should log you out!)_
6. Now, log in to your secured admin dashboard at http://localhost:1337/auth/login.
You did it! 🥳 You've successfully started the bknd server and admin dashboard, created an admin user, and protected your bknd app by enabling the guard.
You did it! You've started the bknd server, created an admin user, and protected your app by enabling the guard.
What you've just experienced is called UI-only mode, where the bknd data and configuration is managed via the admin dashboard UI. Some may prefer the UI-only mode, while others may prefer the code-only mode.
## Modes
### bknd modes
bknd supports multiple modes to suit your needs.
What you just experienced is **UI-only mode** — bknd's data and configuration is managed entirely via the admin dashboard. But that's not the only way to use bknd:
<Cards className="grid-cols-3">
<Card title="UI-only" href="/usage/introduction#ui-only-mode" icon={<SquareMousePointer className="text-fd-primary !size-6" />}>
<Card title="UI-only" href="/usage/setup#ui-only-mode" icon={<SquareMousePointer className="text-fd-primary !size-6" />}>
Configure your backend and manage your data visually with the built-in Admin UI.
</Card>
<Card title="Code-only" href="/usage/introduction#code-only-mode" icon={<Code className="text-fd-primary !size-6" />}>
<Card title="Code-only" href="/usage/setup#code-only-mode" icon={<Code className="text-fd-primary !size-6" />}>
Configure your backend programmatically with a Drizzle-like API, manage your data with the Admin UI.
</Card>
<Card title="Hybrid" href="/usage/introduction#hybrid-mode" icon={<Blend className="text-fd-primary !size-6" />}>
<Card title="Hybrid" href="/usage/setup#hybrid-mode" icon={<Blend className="text-fd-primary !size-6" />}>
Configure your backend visually while in development, use a read-only configuration in production.
</Card>
</Cards>
### Demo bknd in the browser
Learn more about each mode and the underlying configuration in [Setup & Modes](/usage/setup).
Here is a preview of bknd in StackBlitz:
## Try bknd in the browser
<Card className="p-0 pb-1">
<StackBlitz path="github/bknd-io/bknd-demo" initialPath="/" />
@@ -90,7 +96,7 @@ Here is a preview of bknd in StackBlitz:
## Start with a Framework/Runtime
Start by using the integration guide for these popular frameworks/runtimes. There will be more in the future, so stay tuned!
Pick your framework or runtime to get started.
<Cards>
<Card icon={<Icon icon="tabler:brand-nextjs" className="text-fd-primary !size-6" />} title="NextJS" href="/integration/nextjs" />

View File

@@ -263,7 +263,7 @@ To automatically sync your secrets to a file, you may also use the [`syncSecrets
## Syncing the database (`sync`)
Sync your database can be useful when running in [`code`](/usage/introduction/#code-only-mode) mode. When you're ready to deploy, you can point to the production configuration and sync the database. Schema mutations are only applied when running with the `--force` option.
Sync your database can be useful when running in [`code`](/usage/setup/#code-only-mode) mode. When you're ready to deploy, you can point to the production configuration and sync the database. Schema mutations are only applied when running with the `--force` option.
```bash
$ npx bknd sync --help

View File

@@ -310,7 +310,7 @@ const app = createApp({ connection });
## Data Structure
To provide a database structure, you can pass `config` to the creation of an app. In [`db` mode](/usage/introduction#ui-only-mode), the data structure is only respected if the database is empty. If you made updates, ensure to delete the database first, or perform updates through the Admin UI.
To provide a database structure, you can pass `config` to the creation of an app. In [`db` mode](/usage/setup#ui-only-mode), the data structure is only respected if the database is empty. If you made updates, ensure to delete the database first, or perform updates through the Admin UI.
Here is a quick example:
@@ -506,9 +506,9 @@ const app = createApp({
});
```
Note that in [`db` mode](/usage/introduction#ui-only-mode), the seed function will only be executed on app's first boot. If a configuration already exists in the database, it will not be executed.
Note that in [`db` mode](/usage/setup#ui-only-mode), the seed function will only be executed on app's first boot. If a configuration already exists in the database, it will not be executed.
In [`code` mode](/usage/introduction#code-only-mode), the seed function will not be automatically executed. You can manually execute it by running the following command:
In [`code` mode](/usage/setup#code-only-mode), the seed function will not be automatically executed. You can manually execute it by running the following command:
```bash
npx bknd sync --seed --force

View File

@@ -1,264 +0,0 @@
---
title: "Introduction"
description: "Setting up bknd"
icon: Pin
tags: ["documentation"]
---
import { TypeTable } from "fumadocs-ui/components/type-table";
import { SquareMousePointer, Code, Blend } from 'lucide-react';
There are several methods to get **bknd** up and running. You can choose between these options:
1. [Run it using the CLI](/usage/cli): That's the easiest and fastest way to get started.
2. Use a runtime like [Node](/integration/node), [Bun](/integration/bun) or
[Cloudflare](/integration/cloudflare) (workerd). This will run the API and UI in the runtime's
native server and serves the UI assets statically from `node_modules`.
3. Run it inside your React framework of choice like [Next.js](/integration/nextjs),
[Astro](/integration/astro) or [Remix](/integration/remix).
There is also a fourth option, which is running it inside a
[Docker container](/integration/docker). This is essentially a wrapper around the CLI.
## Basic setup
Regardless of the method you choose, at the end all adapters come down to the actual
instantiation of the `App`, which in raw looks like this:
```typescript
import { createApp, type BkndConfig } from "bknd";
// create the app
const config = {
/* ... */
} satisfies BkndConfig;
const app = createApp(config);
// build the app
await app.build();
// export for Web API compliant envs
export default app;
```
In Web API compliant environments, all you have to do is to default exporting the app, as it implements the `Fetch` API. In case an explicit `fetch` export is needed, you can use the `app.fetch` property.
```typescript
const app = /* ... */;
export default {
fetch: app.fetch,
}
```
Check the integration details for your specific runtime or framework in the [integration](/integration/introduction) section.
## Modes
Main project goal is to provide a backend that can be configured visually with the built-in Admin UI. However, you may instead want to configure your backend programmatically, and define your data structure with a Drizzle-like API:
<Cards className="grid-cols-1 sm:grid-cols-2 md:grid-cols-3">
<Card title="UI-only (default)" href="/usage/introduction#ui-only-mode" icon={<SquareMousePointer className="text-fd-primary !size-6" />}>
This is the default mode, it allows visual configuration and saves the configuration to the database. Expects you to deploy your backend separately from your frontend.
</Card>
<Card title="Code-only" href="/usage/introduction#code-only-mode" icon={<Code className="text-fd-primary !size-6" />}>
This mode allows you to configure your backend programmatically, and define your data structure with a Drizzle-like API. Visual configuration controls are disabled.
</Card>
<Card title={"Hybrid"} href="/usage/introduction#hybrid-mode" icon={<Blend className="text-fd-primary !size-6" />}>
This mode allows you to configure your backend visually while in development, and uses the produced configuration in a code-only mode for maximum performance.
</Card>
</Cards>
In the following sections, we'll cover the different modes in more detail. The configuration properties involved are the following:
```typescript title="bknd.config.ts"
import type { BkndConfig } from "bknd";
export default {
config: { /* ... */ }
options: {
mode: "db", // or "code"
manager: {
secrets: { /* ... */ },
storeSecrets: true,
},
}
} satisfies BkndConfig;
```
<TypeTable type={{
config: {
description: "The initial configuration when `mode` is `\"db\"`, and as the produced configuration when `mode` is `\"code\"`.",
type: "object",
properties: {
/* ... */
}
},
["options.mode"]: {
description: "The options for the app.",
type: '"db" | "code"',
default: '"db"'
},
["options.manager.secrets"]: {
description: "The app secrets to be provided when using `\"db\"` mode. This is required since secrets are extracted and stored separately to the database.",
type: "object",
properties: {
/* ... */
}
},
["options.manager.storeSecrets"]: {
description: "Whether to store secrets in the database when using `\"db\"` mode.",
type: "boolean",
default: "true"
}
}} />
### UI-only mode
This mode is the default mode. It allows you to configure your backend visually with the built-in Admin UI. It expects that you deploy your backend separately from your frontend, and make changes there. No configuration is needed, however, if you want to provide an initial configuration, you can do so by passing a `config` object.
```typescript
import type { BkndConfig } from "bknd";
export default {
// this will only be applied if the database is empty
config: { /* ... */ },
} satisfies BkndConfig;
```
<Callout type="info">
Note that when using the default UI-mode, the initial configuration using the `config` property will only be applied if the database is empty.
</Callout>
### Code-only mode
This mode allows you to configure your backend programmatically, and define your data structure with a Drizzle-like API. Visual configuration controls are disabled.
```typescript title="bknd.config.ts"
import { type BkndConfig, em, entity, text, boolean } from "bknd";
import { secureRandomString } from "bknd/utils";
const schema = em({
todos: entity("todos", {
title: text(),
done: boolean(),
}),
});
export default {
// example configuration
config: {
data: schema.toJSON(),
auth: {
enabled: true,
jwt: {
secret: secureRandomString(64),
},
}
},
options: {
// this ensures that the provided configuration is always used
mode: "code",
},
} satisfies BkndConfig;
```
Unlike the UI-only mode, the configuration passed to `config` is always applied. In case you make data structure changes, you may need to sync the schema to the database manually, e.g. using the [sync command](/usage/cli#syncing-the-database-sync).
### Hybrid mode
This mode allows you to configure your backend visually while in development, and uses the produced configuration in a code-only mode for maximum performance. It gives you the best of both worlds.
While in development, we set the mode to `"db"` where the configuration is stored in the database. When it's time to deploy, we export the configuration, and set the mode to `"code"`. While in `"db"` mode, the `config` property interprets the value as an initial configuration to use when the database is empty.
```typescript title="bknd.config.ts"
import type { BkndConfig } from "bknd";
// import your produced configuration
import appConfig from "./appconfig.json" with { type: "json" };
export default {
config: appConfig,
options: {
mode: process.env.NODE_ENV === "development" ? "db" : "code",
manager: {
secrets: process.env
}
},
} satisfies BkndConfig;
```
To keep your config, secrets and types in sync, you can either use the CLI or the plugins.
| Type | Plugin | CLI Command |
|----------------|-----------------------------------------------------------------------|----------------------------|
| Configuration | [`syncConfig`](/extending/plugins/#syncconfig) | [`config`](/usage/cli/#getting-the-configuration-config) |
| Secrets | [`syncSecrets`](/extending/plugins/#syncsecrets) | [`secrets`](/usage/cli/#getting-the-secrets-secrets) |
| Types | [`syncTypes`](/extending/plugins/#synctypes) | [`types`](/usage/cli/#generating-types-types) |
## Mode helpers
To make the setup using your preferred mode easier, there are mode helpers for [`code`](/usage/introduction#code-only-mode) and [`hybrid`](/usage/introduction#hybrid-mode) modes.
* built-in syncing of config, types and secrets
* let bknd automatically sync the data schema in development
* automatically switch modes in hybrid (from db to code) in production
* automatically skip config validation in production to boost performance
To use it, you have to wrap your configuration in a mode helper, e.g. for `code` mode using the Bun adapter:
```typescript title="bknd.config.ts"
import { code, type CodeMode } from "bknd/modes";
import { type BunBkndConfig, writer } from "bknd/adapter/bun";
export default code<BunBkndConfig>({
// some normal bun bknd config
connection: { url: "file:data.db" },
// ...
// a writer is required, to sync the types
writer,
// (optional) mode specific config
isProduction: Bun.env.NODE_ENV === "production",
typesFilePath: "bknd-types.d.ts",
// (optional) e.g. have the schema synced if !isProduction
syncSchema: {
force: true,
drop: true,
}
});
```
Similarily, for `hybrid` mode:
```typescript title="bknd.config.ts"
import { hybrid, type HybridMode } from "bknd/modes";
import { type BunBkndConfig, writer, reader } from "bknd/adapter/bun";
export default hybrid<BunBkndConfig>({
// some normal bun bknd config
connection: { url: "file:data.db" },
// ...
// reader/writer are required, to sync the types and config
writer,
reader,
// supply secrets
secrets: await Bun.file(".env.local").json(),
// (optional) mode specific config
isProduction: Bun.env.NODE_ENV === "production",
typesFilePath: "bknd-types.d.ts",
configFilePath: "bknd-config.json",
// (optional) and have them automatically written if !isProduction
syncSecrets: {
outFile: ".env.local",
format: "env",
includeSecrets: true,
},
// (optional) also have the schema synced if !isProduction
syncSchema: {
force: true,
drop: true,
},
});
```

View File

@@ -0,0 +1,180 @@
---
title: "Setup & Modes"
description: "Choose a mode and get bknd running with your app"
icon: Pin
tags: ["documentation"]
---
import { SquareMousePointer, Code, Blend } from 'lucide-react';
bknd supports three modes. Each mode determines how your backend is configured and where that configuration lives.
## Choose your mode
<Cards className="grid-cols-1 sm:grid-cols-2 md:grid-cols-3">
<Card title="UI-only (default)" href="#ui-only-mode" icon={<SquareMousePointer className="text-fd-primary !size-6" />}>
Configure visually via the Admin UI. Config is stored in the database.
</Card>
<Card title="Code-only" href="#code-only-mode" icon={<Code className="text-fd-primary !size-6" />}>
Define your schema in code with a Drizzle-like API. Config lives in your codebase.
</Card>
<Card title="Hybrid" href="#hybrid-mode" icon={<Blend className="text-fd-primary !size-6" />}>
Use the Admin UI in development, export to code for production.
</Card>
</Cards>
**Not sure which to pick?**
- **UI-only** is best for prototyping and small projects. No code needed — configure everything through the dashboard.
- **Code-only** is best for teams, CI/CD, and version-controlled schemas. You define your data structure programmatically.
- **Hybrid** gives you the best of both — visual configuration while developing, locked-down code config in production.
You can always change modes later. Start with UI-only if you're exploring.
---
## UI-only mode
This is the default. Run bknd and configure everything through the Admin UI. No setup code required beyond a database connection.
```typescript title="bknd.config.ts"
import type { BkndConfig } from "bknd";
export default {
connection: { url: "file:data.db" },
} satisfies BkndConfig;
```
If you want to provide an initial data structure (entities, auth settings, etc.), pass it via `config`. It will only be applied when the database is empty.
```typescript title="bknd.config.ts"
import type { BkndConfig } from "bknd";
export default {
connection: { url: "file:data.db" },
config: {
auth: { enabled: true },
},
} satisfies BkndConfig;
```
<Callout type="info">
In UI-only mode, the `config` property is only applied on first boot. After that, all changes are made through the Admin UI.
</Callout>
**Next step:** Pick your [framework or runtime integration](/integration/introduction) to wire bknd into your app.
---
## Code-only mode
Define your data structure programmatically with a Drizzle-like API. The Admin UI becomes read-only for configuration — you still use it to manage data.
```typescript title="bknd.config.ts"
import { type BkndConfig, em, entity, text, boolean } from "bknd";
import { secureRandomString } from "bknd/utils";
const schema = em({
todos: entity("todos", {
title: text(),
done: boolean(),
}),
});
export default {
connection: { url: "file:data.db" },
config: {
data: schema.toJSON(),
auth: {
enabled: true,
jwt: { secret: secureRandomString(64) },
},
},
options: {
mode: "code",
},
} satisfies BkndConfig;
```
Unlike UI-only mode, the `config` is applied on every boot. If you change the schema, you may need to [sync the database](/usage/cli#syncing-the-database-sync).
**Next step:** Pick your [framework or runtime integration](/integration/introduction) to wire bknd into your app.
---
## Hybrid mode
Use the Admin UI to configure your backend while developing. When you're ready to deploy, export the config and run in code mode for production.
```typescript title="bknd.config.ts"
import type { BkndConfig } from "bknd";
import appConfig from "./appconfig.json" with { type: "json" };
export default {
connection: { url: "file:data.db" },
config: appConfig,
options: {
mode: process.env.NODE_ENV === "development" ? "db" : "code",
manager: {
secrets: process.env,
},
},
} satisfies BkndConfig;
```
To export your config, secrets, and types, use the CLI or plugins:
| What | CLI Command | Plugin |
|------|-------------|--------|
| Configuration | [`bknd config`](/usage/cli/#getting-the-configuration-config) | [`syncConfig`](/extending/plugins/#syncconfig) |
| Secrets | [`bknd secrets`](/usage/cli/#getting-the-secrets-secrets) | [`syncSecrets`](/extending/plugins/#syncsecrets) |
| Types | [`bknd types`](/usage/cli/#generating-types-types) | [`syncTypes`](/extending/plugins/#synctypes) |
**Next step:** Pick your [framework or runtime integration](/integration/introduction) to wire bknd into your app.
---
## Mode helpers
For code and hybrid modes, bknd provides helper functions that handle syncing, mode switching, and schema validation automatically.
```typescript title="bknd.config.ts (code mode with Bun)"
import { code } from "bknd/modes";
import { type BunBkndConfig, writer } from "bknd/adapter/bun";
export default code<BunBkndConfig>({
connection: { url: "file:data.db" },
writer,
isProduction: Bun.env.NODE_ENV === "production",
typesFilePath: "bknd-types.d.ts",
});
```
```typescript title="bknd.config.ts (hybrid mode with Bun)"
import { hybrid } from "bknd/modes";
import { type BunBkndConfig, writer, reader } from "bknd/adapter/bun";
export default hybrid<BunBkndConfig>({
connection: { url: "file:data.db" },
writer,
reader,
secrets: await Bun.file(".env.local").json(),
isProduction: Bun.env.NODE_ENV === "production",
typesFilePath: "bknd-types.d.ts",
configFilePath: "bknd-config.json",
});
```
Mode helpers give you:
- Built-in syncing of config, types, and secrets
- Automatic schema syncing in development
- Automatic mode switching (db → code) in production for hybrid
- Skipped config validation in production for faster boot
---
## Further reading
- [Framework & runtime integrations](/integration/introduction) — wire bknd into Next.js, Astro, Bun, Cloudflare, etc.
- [Database configuration](/usage/database) — choose and configure your SQL database
- [Configuration reference](/extending/config) — full `BkndConfig` API, plugins, events, and lifecycle hooks

View File

@@ -9,4 +9,9 @@ export const redirectsConfig = [
destination: "/start",
permanent: true,
},
{
source: "/usage/introduction",
destination: "/usage/setup",
permanent: true,
},
];