Use a runtime like Node, Bun or
Cloudflare (workerd). This will run the API and UI in the runtime's
native server and serves the UI assets statically from node_modules.
Run it inside your React framework of choice like Next.js,
Astro or Remix.
There is also a fourth option, which is running it inside a
Docker container. This is essentially a wrapper around the CLI.
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:
import { createApp, type BkndConfig } from "bknd";// create the appconst config = { /* ... */} satisfies BkndConfig;const app = createApp(config);// build the appawait app.build();// export for Web API compliant envsexport 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.
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:
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.
import type { BkndConfig } from "bknd";export default { // this will only be applied if the database is empty config: { /* ... */ },} satisfies BkndConfig;
Note that when using the default UI-mode, the initial configuration using the config property will only be applied if the database is empty.
This mode allows you to configure your backend programmatically, and define your data structure with a Drizzle-like API. Visual configuration controls are disabled.
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.
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.
bknd.config.ts
import type { BkndConfig } from "bknd";// import your produced configurationimport 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.
To make the setup using your preferred mode easier, there are mode helpers for code and hybrid 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:
bknd.config.ts
import { code, type CodeMode } from "bknd/modes";import { type BunBkndConfig, writer } from "bknd/adapter/bun";const config = { // some normal bun bknd config connection: { url: "file:test.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, }} satisfies CodeMode<BunBkndConfig>;export default code(config);
Similarily, for hybrid mode:
bknd.config.ts
import { hybrid, type HybridMode } from "bknd/modes";import { type BunBkndConfig, writer, reader } from "bknd/adapter/bun";const config = { // some normal bun bknd config connection: { url: "file:test.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, },} satisfies HybridMode<BunBkndConfig>;export default hybrid(config);