docs: enhance documentation with new modes and plugins

- Updated documentation to include new modes for configuring bknd (UI-only, Code-only, Hybrid).
- Introduced `syncSecrets` plugin example in the extending plugins documentation.
- Added `react-icons` dependency to package.json and package-lock.json.
- Enhanced various documentation pages with icons and improved content structure.
This commit is contained in:
dswbx
2025-09-20 19:57:38 +02:00
parent cd262097dc
commit f2da54c92b
15 changed files with 409 additions and 88 deletions

View File

@@ -116,6 +116,36 @@ export default {
} satisfies BkndConfig;
```
### `syncSecrets`
A simple plugin that writes down the app secrets on boot and each build.
```typescript title="bknd.config.ts"
import { syncSecrets } from "bknd/plugins";
import { writeFile } from "node:fs/promises";
export default {
options: {
plugins: [
syncSecrets({
// whether to enable the plugin, make sure to disable in production
enabled: true,
// your writing function (required)
write: async (secrets) => {
// apply your writing logic, e.g. writing a template file in an env format
await writeFile(
".env.example",
Object.entries(secrets)
.map(([key]) => `${key}=`)
.join("\n")
);
},
}),
]
},
} satisfies BkndConfig;
```
### `showRoutes`
A simple plugin that logs the routes of your app in the console.

View File

@@ -6,29 +6,34 @@ tags: ["documentation"]
import { Icon } from "@iconify/react";
import { examples } from "@/app/_components/StackBlitz";
import { SquareMousePointer, Code, Blend } from 'lucide-react';
Glad you're here! **bknd** is a lightweight, infrastructure agnostic and feature-rich backend that runs in any JavaScript environment.
- Instant backend with full REST API
- Built on Web Standards for maximum compatibility
- Multiple run modes (standalone, runtime, framework)
- Official API and React SDK with type-safety
- React elements for auto-configured authentication and media components
- Multiple ready-made [integrations](/integration/introduction) (standalone, runtime, framework)
- Official [API SDK](/usage/sdk) and [React SDK](/usage/react) with type-safety
- [React elements](/usage/elements) for auto-configured authentication and media components
- Built-in [MCP server](/usage/mcp/overview) for controlling your backend
- Multiple run [modes](/usage/introduction#modes) (ui-only, code-only, hybrid)
## Preview
Here is a preview of **bknd** in StackBlitz:
<StackBlitz path="github/bknd-io/bknd-demo" initialPath="/" />
<Card className="p-0 pb-1">
<StackBlitz path="github/bknd-io/bknd-demo" initialPath="/" />
<Accordions className="m-1">
<Accordion title="What's going on?">
The example shown is starting a [node server](/integration/node) using an [in-memory database](/usage/database#sqlite-in-memory). To ensure there are a few entities defined, it is using an [initial structure](/usage/database#initial-structure) using the prototype methods. Furthermore it uses the [seed option](/usage/database#seeding-the-database) to seed some data in the structure created.
<Accordions>
<Accordion title="What's going on?">
The example shown is starting a [node server](/integration/node) using an [in-memory database](/usage/database#sqlite-in-memory). To ensure there are a few entities defined, it is using an [initial structure](/usage/database#initial-structure) using the prototype methods. Furthermore it uses the [seed option](/usage/database#seeding-the-database) to seed some data in the structure created.
To ensure there are users defined on first boot, it hooks into the `App.Events.AppFirstBoot` event to create them (documentation pending).
To ensure there are users defined on first boot, it hooks into the `App.Events.AppFirstBoot` event to create them (documentation pending).
</Accordion>
</Accordions>
</Accordion>
</Accordions>
</Card>
## Quickstart
@@ -156,3 +161,19 @@ The following databases are currently supported. Request a new integration if yo
Create a new issue to request a new database integration.
</Card>
</Cards>
## Choose a mode
**bknd** supports multiple modes to suit your needs.
<Cards className="grid-cols-3">
<Card title="UI-only" href="/usage/introduction#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" />}>
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" />}>
Configure your backend visually while in development, use a read-only configuration in production.
</Card>
</Cards>

View File

@@ -1,6 +1,7 @@
---
title: "Using the CLI"
description: "How to start a bknd instance using the CLI."
icon: Terminal
tags: ["documentation"]
---
@@ -204,5 +205,81 @@ import type { DB } from "bknd";
type Todo = DB["todos"];
```
All bknd methods that involve your database schema will be automatically typed.
All bknd methods that involve your database schema will be automatically typed. You may use the [`syncTypes`](/extending/plugins/#synctypes) plugin to automatically write the types to a file.
## Getting the configuration (`config`)
To see all available `config` options, execute `npx bknd config --help`.
```
$ npx bknd config --help
Usage: bknd config [options]
get app config
Options:
-c, --config <config> config file
--db-url <db> database url, can be any valid sqlite url
--pretty pretty print
--default use default config
--secrets include secrets in output
--out <file> output file
-h, --help display help for command
```
To get the configuration of your app, and to write it to a file, run the following:
```
npx bknd config --out appconfig.json
```
To get a template configuration instead, run the following:
```
npx bknd config --default
```
To automatically sync your configuration to a file, you may also use the [`syncConfig`](/extending/plugins/#syncconfig) plugin.
## Getting the secrets (`secrets`)
To see all available `secrets` options, execute `npx bknd secrets --help`.
```
$ npx bknd secrets --help
Usage: bknd secrets [options]
get app secrets
Options:
-c, --config <config> config file
--db-url <db> database url, can be any valid sqlite url
--template template output without the actual secrets
--format <format> format output (choices: "json", "env", default:
"json")
--out <file> output file
-h, --help display help for command
```
To automatically sync your secrets to a file, you may also use the [`syncSecrets`](/extending/plugins/#syncsecrets) plugin.
## 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.
```
$ npx bknd sync --help
Usage: bknd sync [options]
sync database
Options:
-c, --config <config> config file
--db-url <db> database url, can be any valid sqlite url
--force perform database syncing operations
--drop include destructive DDL operations
--out <file> output file
--sql use sql output
-h, --help display help for command
```

View File

@@ -1,6 +1,7 @@
---
title: "Database"
description: "Choosing the right database configuration"
icon: Database
tags: ["documentation"]
---

View File

@@ -1,6 +1,7 @@
---
title: "React Elements"
description: "Speed up your frontend development"
icon: Box
tags: ["documentation"]
---

View File

@@ -1,9 +1,13 @@
---
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.
@@ -22,12 +26,12 @@ Regardless of the method you choose, at the end all adapters come down to the ac
instantiation of the `App`, which in raw looks like this:
```typescript
import { createApp, type CreateAppConfig } from "bknd";
import { createApp, type BkndConfig } from "bknd";
// create the app
const config = {
/* ... */
} satisfies CreateAppConfig;
} satisfies BkndConfig;
const app = createApp(config);
// build the app
@@ -40,13 +44,153 @@ 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.
## Configuration (`CreateAppConfig`)
## Modes
The `CreateAppConfig` type is the main configuration object for the `createApp` function. It has
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" 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;
```
### 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;
```
### 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) |
## Configuration (`BkndConfig`)
The `BkndConfig` type is the main configuration object for the `createApp` function. It has
the following properties:
```typescript
import type { App, InitialModuleConfigs, ModuleBuildContext, Connection } from "bknd";
import type { App, InitialModuleConfigs, ModuleBuildContext, Connection, MaybePromise } from "bknd";
import type { Config } from "@libsql/client";
type AppPlugin = (app: App) => Promise<void> | void;
@@ -57,13 +201,19 @@ type ManagerOptions = {
seed?: (ctx: ModuleBuildContext) => Promise<void>;
};
type CreateAppConfig = {
type BkndConfig<Args = any> = {
connection?: Connection | Config;
initialConfig?: InitialModuleConfigs;
config?: InitialModuleConfigs;
options?: {
plugins?: AppPlugin[];
manager?: ManagerOptions;
};
app?: BkndConfig<Args> | ((args: Args) => MaybePromise<BkndConfig<Args>>);
onBuilt?: (app: App) => Promise<void>;
beforeBuild?: (app?: App) => Promise<void>;
buildConfig?: {
sync?: boolean;
}
};
```
@@ -72,18 +222,31 @@ type CreateAppConfig = {
The `connection` property is the main connection object to the database. It can be either an object with libsql config or the actual `Connection` class.
```ts
const connection = {
url: "<url>",
authToken: "<token>",
};
// uses the default SQLite connection depending on the runtime
const connection = { url: "<url>" };
// the same as above, but more explicit
import { sqlite } from "bknd/adapter/sqlite";
const connection = sqlite({ url: "<url>" });
// Node.js SQLite, default on Node.js
import { nodeSqlite } from "bknd/adapter/node";
const connection = nodeSqlite({ url: "<url>" });
// Bun SQLite, default on Bun
import { bunSqlite } from "bknd/adapter/bun";
const connection = bunSqlite({ url: "<url>" });
// LibSQL, default on Cloudflare
import { libsql } from "bknd";
const connection = libsql({ url: "<url>" });
```
Alternatively, you can pass an instance of a `Connection` class directly,
see [Custom Connection](/usage/database#custom-connection) as a reference.
See a full list of available connections in the [Database](/usage/database) section. Alternatively, you can pass an instance of a `Connection` class directly, see [Custom Connection](/usage/database#custom-connection) as a reference.
If the connection object is omitted, the app will try to use an in-memory database.
### `initialConfig`
### `config`
As [initial configuration](/usage/database#initial-structure), 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
@@ -157,10 +320,10 @@ to the latest version upon boot. The default configuration looks like this:
}
```
You can use the CLI to get the default configuration:
You can use the [CLI](/usage/cli/#getting-the-configuration-config) to get the default configuration:
```sh
npx bknd config --pretty
npx bknd config --default --pretty
```
To validate your configuration against a JSON schema, you can also dump the schema using the CLI:
@@ -191,6 +354,10 @@ make sure to only run trusted ones.
### `options.seed`
<Callout type="info">
The seed function will only be executed on app's first boot in `"db"` mode. If a configuration already exists in the database, or in `"code"` mode, it will not be executed.
</Callout>
The `seed` property is a function that is called when the app is booted for the first time. It is used to seed the database with initial data. The function is passed a `ModuleBuildContext` object:
```ts
@@ -211,18 +378,3 @@ const seed = async (ctx: ModuleBuildContext) => {
};
```
### `options.manager`
This object is passed to the `ModuleManager` which is responsible for:
- validating and maintaining configuration of all modules
- building all modules (data, auth, media, flows)
- maintaining the `ModuleBuildContext` used by the modules
The `options.manager` object has the following properties:
- `basePath` (`string`): The base path for the Hono instance. This is used to prefix all routes.
- `trustFetched` (`boolean`): If set to `true`, the app will not perform any validity checks for
the given or fetched configuration.
- `onFirstBoot` (`() => Promise<void>`): A function that is called when the app is booted for
the first time.

View File

@@ -1,4 +1,5 @@
{
"title": "MCP",
"icon": "Mcp",
"pages": ["overview", "tools-resources"]
}

View File

@@ -1,5 +1,5 @@
---
title: "MCP"
title: "Tools & Resources"
description: "Tools & Resources of the built-in full featured MCP server."
tags: ["documentation"]
---

View File

@@ -1,6 +1,7 @@
---
title: "SDK (React)"
description: "Use the bknd SDK for React"
icon: React
tags: ["documentation"]
---

View File

@@ -1,6 +1,7 @@
---
title: "SDK (TypeScript)"
description: "Use the bknd SDK in TypeScript"
icon: TypeScript
tags: ["documentation"]
---

25
docs/lib/icons.tsx Normal file
View File

@@ -0,0 +1,25 @@
import { icons } from "lucide-react";
import { TbBrandReact, TbBrandTypescript } from "react-icons/tb";
const McpIcon = () => (
<svg
fill="currentColor"
fillRule="evenodd"
height="1em"
style={{ flex: "none", lineHeight: "1" }}
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<title>ModelContextProtocol</title>
<path d="M15.688 2.343a2.588 2.588 0 00-3.61 0l-9.626 9.44a.863.863 0 01-1.203 0 .823.823 0 010-1.18l9.626-9.44a4.313 4.313 0 016.016 0 4.116 4.116 0 011.204 3.54 4.3 4.3 0 013.609 1.18l.05.05a4.115 4.115 0 010 5.9l-8.706 8.537a.274.274 0 000 .393l1.788 1.754a.823.823 0 010 1.18.863.863 0 01-1.203 0l-1.788-1.753a1.92 1.92 0 010-2.754l8.706-8.538a2.47 2.47 0 000-3.54l-.05-.049a2.588 2.588 0 00-3.607-.003l-7.172 7.034-.002.002-.098.097a.863.863 0 01-1.204 0 .823.823 0 010-1.18l7.273-7.133a2.47 2.47 0 00-.003-3.537z" />
<path d="M14.485 4.703a.823.823 0 000-1.18.863.863 0 00-1.204 0l-7.119 6.982a4.115 4.115 0 000 5.9 4.314 4.314 0 006.016 0l7.12-6.982a.823.823 0 000-1.18.863.863 0 00-1.204 0l-7.119 6.982a2.588 2.588 0 01-3.61 0 2.47 2.47 0 010-3.54l7.12-6.982z" />
</svg>
);
export default {
...icons,
React: TbBrandReact,
TypeScript: TbBrandTypescript,
Mcp: McpIcon,
};

View File

@@ -1,23 +1,29 @@
import { loader } from "fumadocs-core/source";
import { docs } from "@/.source";
import { createOpenAPI, attachFile } from "fumadocs-openapi/server";
import { icons } from "lucide-react";
import icons from "./icons";
import { createElement } from "react";
import { TbBrandReact, TbBrandTypescript } from "react-icons/tb";
const add_icons = {
TypeScript: TbBrandTypescript,
React: TbBrandReact,
};
export const source = loader({
baseUrl: "/",
source: docs.toFumadocsSource(),
pageTree: {
// adds a badge to each page item in page tree
attachFile,
},
icon(icon) {
if (!icon) {
// You may set a default icon
return;
}
if (icon in icons) return createElement(icons[icon as keyof typeof icons]);
},
baseUrl: "/",
source: docs.toFumadocsSource(),
pageTree: {
// adds a badge to each page item in page tree
attachFile,
},
icon(icon) {
if (!icon) {
// You may set a default icon
return;
}
if (icon in icons) return createElement(icons[icon as keyof typeof icons]);
},
});
export const openapi = createOpenAPI();

10
docs/package-lock.json generated
View File

@@ -23,6 +23,7 @@
"next": "15.3.5",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-icons": "^5.5.0",
"tailwind-merge": "^3.3.1",
"twoslash": "^0.3.2"
},
@@ -10352,6 +10353,15 @@
"react": "^16.8.0 || ^17 || ^18 || ^19"
}
},
"node_modules/react-icons": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
"integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
"license": "MIT",
"peerDependencies": {
"react": "*"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",

View File

@@ -31,6 +31,7 @@
"next": "15.3.5",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-icons": "^5.5.0",
"tailwind-merge": "^3.3.1",
"twoslash": "^0.3.2"
},

View File

@@ -1,11 +1,5 @@
import { remarkInstall } from "fumadocs-docgen";
import {
defineConfig,
defineDocs,
frontmatterSchema,
metaSchema,
} from "fumadocs-mdx/config";
import { defineConfig, defineDocs, frontmatterSchema, metaSchema } from "fumadocs-mdx/config";
import { transformerTwoslash } from "fumadocs-twoslash";
import { createFileSystemTypesCache } from "fumadocs-twoslash/cache-fs";
import { rehypeCodeDefaultOptions } from "fumadocs-core/mdx-plugins";
@@ -14,31 +8,31 @@ import { remarkAutoTypeTable } from "fumadocs-typescript";
// You can customise Zod schemas for frontmatter and `meta.json` here
// see https://fumadocs.vercel.app/docs/mdx/collections#define-docs
export const docs = defineDocs({
docs: {
schema: frontmatterSchema,
},
meta: {
schema: metaSchema,
},
docs: {
schema: frontmatterSchema,
},
meta: {
schema: metaSchema,
},
});
export default defineConfig({
mdxOptions: {
remarkPlugins: [remarkInstall, remarkAutoTypeTable],
rehypeCodeOptions: {
lazy: true,
experimentalJSEngine: true,
langs: ["ts", "js", "html", "tsx", "mdx"],
themes: {
light: "light-plus",
dark: "dark-plus",
mdxOptions: {
remarkPlugins: [remarkInstall, remarkAutoTypeTable],
rehypeCodeOptions: {
lazy: true,
experimentalJSEngine: true,
langs: ["ts", "js", "html", "tsx", "mdx"],
themes: {
light: "light-plus",
dark: "dark-plus",
},
transformers: [
...(rehypeCodeDefaultOptions.transformers ?? []),
transformerTwoslash({
typesCache: createFileSystemTypesCache(),
}),
],
},
transformers: [
...(rehypeCodeDefaultOptions.transformers ?? []),
transformerTwoslash({
typesCache: createFileSystemTypesCache(),
}),
],
},
},
},
});