mirror of
https://github.com/bknd-io/bknd/
synced 2026-08-02 08:06:00 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 224d98879a | |||
| d1522c97ea | |||
| 3d5c2cd3a8 | |||
| fc60dfd042 | |||
| c8ee30b3da | |||
| b85c3a8a9b | |||
| e20c2ef92c | |||
| 5873baad3c | |||
| aca162f9c8 | |||
| 9f0997c28a | |||
| 68063986d1 | |||
| 53f8609e9d | |||
| 833bfa5049 | |||
| defcdfef0b | |||
| 348ac7f8b0 | |||
| 16b1cde8ea | |||
| 436b195242 | |||
| ccd504440e | |||
| 023f5391e4 | |||
| 9685c5d4e1 | |||
| 9b008be512 | |||
| 5352331464 | |||
| 6651fce099 | |||
| 91a1fc8a0c |
@@ -1,66 +1,76 @@
|
|||||||
import { expect, describe, it, beforeAll, afterAll, mock } from "bun:test";
|
import { expect, describe, it, beforeAll, afterAll, mock } from "bun:test";
|
||||||
import * as adapter from "adapter";
|
import * as adapter from "adapter";
|
||||||
import { disableConsoleLog, enableConsoleLog } from "core/utils";
|
import { disableConsoleLog, enableConsoleLog, omitKeys } from "core/utils";
|
||||||
import { adapterTestSuite } from "adapter/adapter-test-suite";
|
import { adapterTestSuite } from "adapter/adapter-test-suite";
|
||||||
import { bunTestRunner } from "adapter/bun/test";
|
import { bunTestRunner } from "adapter/bun/test";
|
||||||
import { omitKeys } from "core/utils";
|
|
||||||
|
const stripConnection = <T extends Record<string, any>>(cfg: T) =>
|
||||||
|
omitKeys(cfg, ["connection"]);
|
||||||
|
|
||||||
beforeAll(disableConsoleLog);
|
beforeAll(disableConsoleLog);
|
||||||
afterAll(enableConsoleLog);
|
afterAll(enableConsoleLog);
|
||||||
|
|
||||||
describe("adapter", () => {
|
describe("adapter", () => {
|
||||||
it("makes config", async () => {
|
describe("makeConfig", () => {
|
||||||
expect(omitKeys(await adapter.makeConfig({}), ["connection"])).toEqual({});
|
it("returns empty config for empty inputs", async () => {
|
||||||
expect(
|
const cases: Array<Parameters<typeof adapter.makeConfig>> = [
|
||||||
omitKeys(await adapter.makeConfig({}, { env: { TEST: "test" } }), ["connection"]),
|
[{}],
|
||||||
).toEqual({});
|
[{}, { env: { TEST: "test" } }],
|
||||||
|
];
|
||||||
|
|
||||||
// merges everything returned from `app` with the config
|
for (const args of cases) {
|
||||||
expect(
|
const cfg = await adapter.makeConfig(...(args as any));
|
||||||
omitKeys(
|
expect(stripConnection(cfg)).toEqual({});
|
||||||
await adapter.makeConfig(
|
}
|
||||||
{ app: (a) => ({ config: { server: { cors: { origin: a.env.TEST } } } }) },
|
});
|
||||||
{ env: { TEST: "test" } },
|
|
||||||
),
|
|
||||||
["connection"],
|
|
||||||
),
|
|
||||||
).toEqual({
|
|
||||||
config: { server: { cors: { origin: "test" } } },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows all properties in app function", async () => {
|
it("merges app output into config", async () => {
|
||||||
const called = mock(() => null);
|
const cfg = await adapter.makeConfig(
|
||||||
const config = await adapter.makeConfig(
|
{ app: (a) => ({ config: { server: { cors: { origin: a.env.TEST } } } }) },
|
||||||
{
|
{ env: { TEST: "test" } },
|
||||||
app: (env) => ({
|
|
||||||
connection: { url: "test" },
|
|
||||||
config: { server: { cors: { origin: "test" } } },
|
|
||||||
options: {
|
|
||||||
mode: "db",
|
|
||||||
},
|
|
||||||
onBuilt: () => {
|
|
||||||
called();
|
|
||||||
expect(env).toEqual({ foo: "bar" });
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{ foo: "bar" },
|
|
||||||
);
|
);
|
||||||
expect(config.connection).toEqual({ url: "test" });
|
|
||||||
expect(config.config).toEqual({ server: { cors: { origin: "test" } } });
|
|
||||||
expect(config.options).toEqual({ mode: "db" });
|
|
||||||
await config.onBuilt?.(null as any);
|
|
||||||
expect(called).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
adapterTestSuite(bunTestRunner, {
|
expect(stripConnection(cfg)).toEqual({
|
||||||
|
config: { server: { cors: { origin: "test" } } },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows all properties in app() result", async () => {
|
||||||
|
const called = mock(() => null);
|
||||||
|
|
||||||
|
const cfg = await adapter.makeConfig(
|
||||||
|
{
|
||||||
|
app: (env) => ({
|
||||||
|
connection: { url: "test" },
|
||||||
|
config: { server: { cors: { origin: "test" } } },
|
||||||
|
options: { mode: "db" as const },
|
||||||
|
onBuilt: () => {
|
||||||
|
called();
|
||||||
|
expect(env).toEqual({ foo: "bar" });
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{ foo: "bar" },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(cfg.connection).toEqual({ url: "test" });
|
||||||
|
expect(cfg.config).toEqual({ server: { cors: { origin: "test" } } });
|
||||||
|
expect(cfg.options).toEqual({ mode: "db" });
|
||||||
|
|
||||||
|
await cfg.onBuilt?.({} as any);
|
||||||
|
expect(called).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("adapter test suites", () => {
|
||||||
|
adapterTestSuite(bunTestRunner, {
|
||||||
makeApp: adapter.createFrameworkApp,
|
makeApp: adapter.createFrameworkApp,
|
||||||
label: "framework app",
|
label: "framework app",
|
||||||
});
|
});
|
||||||
|
|
||||||
adapterTestSuite(bunTestRunner, {
|
adapterTestSuite(bunTestRunner, {
|
||||||
makeApp: adapter.createRuntimeApp,
|
makeApp: adapter.createRuntimeApp,
|
||||||
label: "runtime app",
|
label: "runtime app",
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+13
-2
@@ -2,7 +2,7 @@ import { $ } from "bun";
|
|||||||
import * as tsup from "tsup";
|
import * as tsup from "tsup";
|
||||||
import pkg from "./package.json" with { type: "json" };
|
import pkg from "./package.json" with { type: "json" };
|
||||||
import c from "picocolors";
|
import c from "picocolors";
|
||||||
import { watch as fsWatch } from "node:fs";
|
import { watch as fsWatch, readdirSync, rmSync } from "node:fs";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
|
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
@@ -27,7 +27,18 @@ const define = {
|
|||||||
|
|
||||||
if (clean) {
|
if (clean) {
|
||||||
console.info("Cleaning dist (w/o static)");
|
console.info("Cleaning dist (w/o static)");
|
||||||
await $`find dist -mindepth 1 ! -path "dist/static/*" ! -path "dist/static" -exec rm -rf {} +`;
|
// Cross-platform clean: remove all files/folders in dist except static
|
||||||
|
const distPath = join(import.meta.dir, "dist");
|
||||||
|
try {
|
||||||
|
const entries = readdirSync(distPath);
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry === "static") continue;
|
||||||
|
const entryPath = join(distPath, entry);
|
||||||
|
rmSync(entryPath, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// dist may not exist yet, ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let types_running = false;
|
let types_running = false;
|
||||||
|
|||||||
+1
-1
@@ -46,7 +46,7 @@
|
|||||||
"test:e2e:report": "VITE_DB_URL=:memory: playwright show-report",
|
"test:e2e:report": "VITE_DB_URL=:memory: playwright show-report",
|
||||||
"docs:build-assets": "bun internal/docs.build-assets.ts"
|
"docs:build-assets": "bun internal/docs.build-assets.ts"
|
||||||
},
|
},
|
||||||
"license": "FSL-1.1-MIT",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cfworker/json-schema": "^4.1.1",
|
"@cfworker/json-schema": "^4.1.1",
|
||||||
"@codemirror/lang-html": "^6.4.11",
|
"@codemirror/lang-html": "^6.4.11",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"name": "bknd",
|
"name": "bknd",
|
||||||
"version": "0.20.0-rc.1",
|
"version": "0.20.0",
|
||||||
"bin": "./dist/cli/index.js",
|
"bin": "./dist/cli/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cfworker/json-schema": "^4.1.1",
|
"@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=="],
|
"@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=="],
|
"@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=="],
|
"@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=="],
|
"@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=="],
|
"@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-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=="],
|
"@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=="],
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { TypeTable } from "fumadocs-ui/components/type-table";
|
|||||||
|
|
||||||
|
|
||||||
bknd features an integrated Admin UI that can be used to:
|
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 database contents
|
||||||
- manage your media contents
|
- manage your media contents
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
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
|
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
|
```json
|
||||||
{
|
{
|
||||||
@@ -210,6 +210,10 @@ to the latest version upon boot ([`db` mode](/usage/introduction#ui-only-mode) o
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<Callout type="warn" title="Guard lockout warning">
|
||||||
|
Setting `auth.guard.enabled` to `true` without first creating a user with an admin role will lock you out of the admin portal. See [Securing Your Admin Portal](/modules/auth#securing-your-admin-portal) for the required steps before enabling the Guard.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
You can use the [CLI](/usage/cli/#getting-the-configuration-config) to get the default configuration:
|
You can use the [CLI](/usage/cli/#getting-the-configuration-config) to get the default configuration:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ Now you can use the CLI with your Cloudflare resources.
|
|||||||
Instead, it's recommended to split this configuration into separate files, e.g. `bknd.config.ts` and `config.ts`:
|
Instead, it's recommended to split this configuration into separate files, e.g. `bknd.config.ts` and `config.ts`:
|
||||||
|
|
||||||
```typescript title="config.ts"
|
```typescript title="config.ts"
|
||||||
import type { CloudflareBkndConfig } from "bknd/adapter/cloudflare";
|
import { d1, type CloudflareBkndConfig } from "bknd/adapter/cloudflare";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
app: (env) => ({
|
app: (env) => ({
|
||||||
@@ -236,4 +236,4 @@ As an additional safe guard, you have to set a `PROXY` environment variable to `
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
PROXY=1 npx bknd types
|
PROXY=1 npx bknd types
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"start",
|
"start",
|
||||||
"motivation",
|
"motivation",
|
||||||
"---Usage---",
|
"---Usage---",
|
||||||
"./usage/introduction",
|
"./usage/setup",
|
||||||
"./usage/database",
|
"./usage/database",
|
||||||
"./usage/cli",
|
"./usage/cli",
|
||||||
"./usage/sdk",
|
"./usage/sdk",
|
||||||
|
|||||||
@@ -27,3 +27,50 @@ Authentication is essential for securing applications, and **bknd** provides a s
|
|||||||
- Compatible with any specification-compliant provider.
|
- Compatible with any specification-compliant provider.
|
||||||
|
|
||||||
With a focus on flexibility and ease of integration, bknd's authentication system offers the essentials for managing secure user access in your applications.
|
With a focus on flexibility and ease of integration, bknd's authentication system offers the essentials for managing secure user access in your applications.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Securing Your Admin Portal
|
||||||
|
|
||||||
|
<Callout type="warn" title="Do not enable the Guard without an admin user">
|
||||||
|
Enabling the Guard without first creating a user with an admin role **will lock you out of the admin portal entirely**. There is no login screen that can save you — you'll need to manually edit the database to recover. Follow the checklist below before enabling the Guard.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
The **Guard** protects your admin portal and API endpoints by requiring authentication and proper permissions. Before enabling it, you must set up at least one user with full admin access.
|
||||||
|
|
||||||
|
### Checklist Before Enabling Guard
|
||||||
|
|
||||||
|
Complete these steps **in order** before turning on the Guard:
|
||||||
|
|
||||||
|
1. **Create an admin role** with `implicit_allow: true`
|
||||||
|
- This grants full access to all permissions
|
||||||
|
- Go to Auth → Roles → Create a new role
|
||||||
|
- Enable the "Implicit Allow" toggle
|
||||||
|
|
||||||
|
2. **Create a user**
|
||||||
|
- Go to Auth → Users → Create a new user
|
||||||
|
- Set up their email and password
|
||||||
|
|
||||||
|
3. **Attach the admin role to the user**
|
||||||
|
- Edit the user you just created
|
||||||
|
- Assign the admin role to them
|
||||||
|
|
||||||
|
4. **Verify you can sign in**
|
||||||
|
- Open an incognito/private browser window
|
||||||
|
- Navigate to your app and sign in with the admin user
|
||||||
|
- Confirm you have access
|
||||||
|
|
||||||
|
5. **Now enable the Guard**
|
||||||
|
- Go to Auth → Settings
|
||||||
|
- Enable the Guard
|
||||||
|
|
||||||
|
### Recovery: If You're Locked Out
|
||||||
|
|
||||||
|
If you enabled the Guard without setting up an admin user, you'll need to access your database directly:
|
||||||
|
|
||||||
|
1. Connect to your database using a database client or CLI tool
|
||||||
|
2. Find the `__bknd` table
|
||||||
|
3. Locate the row where `type = 'config'`
|
||||||
|
4. In the `json` column, set `auth.guard.enabled` to `false`
|
||||||
|
5. Restart your bknd instance
|
||||||
|
6. Complete the checklist above, then re-enable the Guard
|
||||||
|
|||||||
@@ -6,38 +6,36 @@ tags: ["documentation"]
|
|||||||
|
|
||||||
import { Icon } from "@iconify/react";
|
import { Icon } from "@iconify/react";
|
||||||
import { examples } from "@/app/_components/StackBlitz";
|
import { examples } from "@/app/_components/StackBlitz";
|
||||||
import { SquareMousePointer, Code, Blend } from 'lucide-react';
|
import { SquareMousePointer, Code, Blend, Rocket } from 'lucide-react';
|
||||||
|
|
||||||
|
## bknd is a lightweight batteries-included backend that embeds into your frontend app
|
||||||
|
|
||||||
Glad you're here! **bknd** is a lightweight, infrastructure agnostic and feature-rich backend that runs in any JavaScript environment.
|
<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>
|
||||||
|
|
||||||
- Instant backend with full REST API
|
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.
|
||||||
- Built on Web Standards for maximum compatibility
|
|
||||||
- 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
|
Bring your [favorite frontend](./#start-with-a-frameworkruntime) and [favorite SQL database](./#use-your-favorite-sql-database), and we'll bring the ~~backend~~ bknd.
|
||||||
|
|
||||||
Here is a preview of **bknd** in StackBlitz:
|
<Cards>
|
||||||
|
<Card
|
||||||
<Card className="p-0 pb-1">
|
href="/motivation"
|
||||||
<StackBlitz path="github/bknd-io/bknd-demo" initialPath="/" />
|
title="Learn about why we built bknd"
|
||||||
<Accordions className="m-1">
|
icon={<Rocket />}
|
||||||
<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.
|
Why another backend system?
|
||||||
|
</Card>
|
||||||
To ensure there are users defined on first boot, it hooks into the `App.Events.AppFirstBoot` event to create them (documentation pending).
|
</Cards>
|
||||||
|
|
||||||
</Accordion>
|
|
||||||
</Accordions>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
Enter the following command to spin up an instance:
|
<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>
|
||||||
|
|
||||||
|
Spin up a bknd instance via the [bknd CLI](/usage/cli):
|
||||||
|
|
||||||
<Tabs groupId='package-manager' persist items={[ 'npm','bun' ]}>
|
<Tabs groupId='package-manager' persist items={[ 'npm','bun' ]}>
|
||||||
|
|
||||||
@@ -51,11 +49,54 @@ bunx bknd run
|
|||||||
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
To learn more about the CLI, check out the [Using the CLI](/usage/cli) guide.
|
This creates a local `data.db` SQLite database and starts the bknd web server at http://localhost:1337.
|
||||||
|
|
||||||
|
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.
|
||||||
|
3. Visit http://localhost:1337/auth/roles. Create an admin role by selecting "Create new". Name it "Admin" and select "Create". Then, scroll to the bottom of the page and enable "Implicit allow missing permissions?". Select "Update" to save.
|
||||||
|
4. Visit http://localhost:1337/data/entity/users. Select on your newly created user, select the "Admin" role, and select "Update".
|
||||||
|
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 started the bknd server, created an admin user, and protected your app by enabling the guard.
|
||||||
|
|
||||||
|
## Modes
|
||||||
|
|
||||||
|
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/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/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/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>
|
||||||
|
|
||||||
|
Learn more about each mode and the underlying configuration in [Setup & Modes](/usage/setup).
|
||||||
|
|
||||||
|
## Try bknd in the browser
|
||||||
|
|
||||||
|
<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.
|
||||||
|
|
||||||
|
To ensure there are users defined on first boot, it hooks into the `App.Events.AppFirstBoot` event to create them (documentation pending).
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
</Accordions>
|
||||||
|
</Card>
|
||||||
|
|
||||||
## Start with a Framework/Runtime
|
## 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>
|
<Cards>
|
||||||
<Card icon={<Icon icon="tabler:brand-nextjs" className="text-fd-primary !size-6" />} title="NextJS" href="/integration/nextjs" />
|
<Card icon={<Icon icon="tabler:brand-nextjs" className="text-fd-primary !size-6" />} title="NextJS" href="/integration/nextjs" />
|
||||||
@@ -116,7 +157,7 @@ Start by using the integration guide for these popular frameworks/runtimes. Ther
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
icon={<Icon icon="simple-icons:vitest" className="text-fd-primary !size-6" />}
|
icon={<Icon icon="simple-icons:vite" className="text-fd-primary !size-6" />}
|
||||||
title="Vite"
|
title="Vite"
|
||||||
href="/integration/vite"
|
href="/integration/vite"
|
||||||
/>
|
/>
|
||||||
@@ -173,19 +214,3 @@ The following databases are currently supported. Request a new integration if yo
|
|||||||
Create a new issue to request a new database integration.
|
Create a new issue to request a new database integration.
|
||||||
</Card>
|
</Card>
|
||||||
</Cards>
|
</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>
|
|
||||||
@@ -263,7 +263,7 @@ To automatically sync your secrets to a file, you may also use the [`syncSecrets
|
|||||||
|
|
||||||
## Syncing the database (`sync`)
|
## 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
|
```bash
|
||||||
$ npx bknd sync --help
|
$ npx bknd sync --help
|
||||||
|
|||||||
@@ -310,7 +310,7 @@ const app = createApp({ connection });
|
|||||||
|
|
||||||
## Data Structure
|
## 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:
|
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
|
```bash
|
||||||
npx bknd sync --seed --force
|
npx bknd sync --seed --force
|
||||||
|
|||||||
@@ -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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
@@ -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
|
||||||
@@ -9,3 +9,7 @@ tags: ["guide"]
|
|||||||
[Discord](https://discord.gg/952SFk8Tb8).
|
[Discord](https://discord.gg/952SFk8Tb8).
|
||||||
</Callout>
|
</Callout>
|
||||||
|
|
||||||
|
<Callout type="warn" title="Set up permissions before enabling the Guard">
|
||||||
|
If you enable the Guard without first creating an admin role (with `implicit_allow: true`) and attaching it to a user, you will be locked out of the admin portal. See [Securing Your Admin Portal](/modules/auth#securing-your-admin-portal) for the full checklist.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
|||||||
@@ -9,3 +9,7 @@ tags: ["guide"]
|
|||||||
[Discord](https://discord.gg/952SFk8Tb8).
|
[Discord](https://discord.gg/952SFk8Tb8).
|
||||||
</Callout>
|
</Callout>
|
||||||
|
|
||||||
|
<Callout type="warn" title="Set up roles before enabling the Guard">
|
||||||
|
If you enable the Guard without first creating a role with `implicit_allow: true` and attaching it to a user, you will be locked out of the admin portal. See [Securing Your Admin Portal](/modules/auth#securing-your-admin-portal) for the full checklist.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
|||||||
@@ -9,4 +9,9 @@ export const redirectsConfig = [
|
|||||||
destination: "/start",
|
destination: "/start",
|
||||||
permanent: true,
|
permanent: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
source: "/usage/introduction",
|
||||||
|
destination: "/usage/setup",
|
||||||
|
permanent: true,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"@astrojs/react": "^4.2.2",
|
"@astrojs/react": "^4.2.2",
|
||||||
"@types/react": "^19.0.12",
|
"@types/react": "^19.0.12",
|
||||||
"@types/react-dom": "^19.0.4",
|
"@types/react-dom": "^19.0.4",
|
||||||
"astro": "^4.16.16",
|
"astro": "^5.16.9",
|
||||||
"bknd": "file:../../app",
|
"bknd": "file:../../app",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user