Files
bknd/docs/content/docs/(documentation)/usage/introduction.mdx
dswbx a2fa11ccd0 refactor modes implementation and improve validation handling
refactor `code` and `hybrid` modes for better type safety and configuration flexibility. add `_isProd` helper to standardize environment checks and improve plugin syncing warnings. adjust validation logic for clean JSON schema handling and enhance test coverage for modes.
2025-11-20 21:08:16 +01:00

264 lines
9.5 KiB
Plaintext

---
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,
},
});
```