Merge pull request 'Release/0.21' (#6) from release/0.21 into main
Run Tests / test (push) Successful in 3m53s
Run Tests / test (push) Successful in 3m53s
Reviewed-on: shishantbiswas/bknd#6
This commit was merged in pull request #6.
This commit is contained in:
@@ -335,3 +335,55 @@ export default {
|
||||
```
|
||||
|
||||
<AutoTypeTable path="../app/src/plugins/auth/email-otp.plugin.ts" name="EmailOTPPluginOptions" />
|
||||
|
||||
|
||||
### `sort`
|
||||
|
||||
A plugin that maintains sequential numeric positions for one or more entities. Positions are updated automatically when items are inserted or moved. You can optionally configure a scope so that positions are maintained independently within groups, such as tasks within a project.
|
||||
|
||||
```typescript title="bknd.config.ts"
|
||||
import { sort } from "bknd/plugins";
|
||||
|
||||
export default {
|
||||
options: {
|
||||
plugins: [
|
||||
sort({
|
||||
entities: {
|
||||
tasks: {
|
||||
field: "position",
|
||||
scope: "project_id",
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
} satisfies BkndConfig;
|
||||
```
|
||||
|
||||
The configured `field` is used for the numeric position. If the field does not exist, the plugin adds it as a number field with a default value of `0`; if it already exists, it must be a number field. The plugin also adds an index for the field when one is not already present. The optional `scope` field limits reordering to records with the same scope value.
|
||||
|
||||
The plugin registers the following endpoints. The default base path is `/api/sort` and can be changed with `apiBasePath`:
|
||||
|
||||
- `POST /api/sort/:entity/reorder` moves an item to a position. The request body must include the item `id` and its new numeric `position`.
|
||||
- `POST /api/sort/:entity/recalculate` recalculates positions in ascending order of the configured field. To recalculate only one scope, pass its value in the optional `scope` property.
|
||||
|
||||
```http title="Move a task"
|
||||
POST /api/sort/tasks/reorder
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": "task_123",
|
||||
"position": 2
|
||||
}
|
||||
```
|
||||
|
||||
```http title="Recalculate a project's task positions"
|
||||
POST /api/sort/tasks/recalculate
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"scope": "project_456"
|
||||
}
|
||||
```
|
||||
|
||||
<AutoTypeTable path="../app/src/plugins/data/sort.plugin.ts" name="SortPluginOptions" />
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
"astro",
|
||||
"sveltekit",
|
||||
"tanstack-start",
|
||||
"vite"
|
||||
"vite",
|
||||
"nuxt",
|
||||
"solid-start"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,402 @@
|
||||
---
|
||||
title: "Nuxt"
|
||||
description: "Run bknd inside Nuxt"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
To get started with Nuxt and bknd, create a new Nuxt project by following the [official guide](https://nuxt.com/docs/4.x/getting-started/installation), and then install bknd as a dependency:
|
||||
|
||||
<Tabs groupId='package-manager' persist items={[ 'npm', 'pnpm', 'yarn', 'bun']}>
|
||||
|
||||
```bash tab="npm"
|
||||
npm install bknd
|
||||
```
|
||||
|
||||
```bash tab="pnpm"
|
||||
pnpm install bknd
|
||||
```
|
||||
|
||||
```bash tab="yarn"
|
||||
yarn add bknd
|
||||
```
|
||||
|
||||
```bash tab="bun"
|
||||
bun add bknd
|
||||
```
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Configuration
|
||||
|
||||
<Callout type="warning">
|
||||
When run with Node.js, a version of 22 (LTS) or higher is required. Please
|
||||
verify your version by running `node -v`, and
|
||||
[upgrade](https://nodejs.org/en/download/) if necessary.
|
||||
</Callout>
|
||||
|
||||
Now create a `bknd.config.ts` file in the root of your project:
|
||||
|
||||
```typescript title="bknd.config.ts"
|
||||
import type NuxtBkndConfig from "bknd/adapter/nuxt";
|
||||
import { 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(32),
|
||||
},
|
||||
},
|
||||
},
|
||||
options: {
|
||||
// the seed option is only executed if the database was empty
|
||||
seed: async (ctx) => {
|
||||
// create some entries
|
||||
await ctx.em.mutator("todos").insertMany([
|
||||
{ title: "Learn bknd", done: true },
|
||||
{ title: "Build something cool", done: false },
|
||||
]);
|
||||
|
||||
// and create a user
|
||||
await ctx.app.module.auth.createUser({
|
||||
email: "test@bknd.io",
|
||||
password: "12345678",
|
||||
});
|
||||
},
|
||||
},
|
||||
} satisfies NuxtBkndConfig;
|
||||
```
|
||||
|
||||
For more information about the connection object, refer to the [Database](/usage/database) guide.
|
||||
|
||||
|
||||
See [bknd.config.ts](/extending/config) for more information on how to configure bknd. The `NuxtBkndConfig` type extends the base config type with the following properties:
|
||||
|
||||
```typescript
|
||||
export type NuxtBkndConfig<Env = NuxtEnv> = FrameworkBkndConfig<Env>;
|
||||
```
|
||||
|
||||
## Serve the API and Admin UI
|
||||
|
||||
The Nuxt adapter uses Nuxt middleware to handle API requests and serve the Admin UI. Create a `/server/middleware/bknd.ts` file:
|
||||
|
||||
```typescript title="/server/middleware/bknd.ts"
|
||||
import { serve } from "bknd/adapter/nuxt";
|
||||
import config from "../../bknd.config";
|
||||
|
||||
const handler = serve(config, process.env);
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const pathname = event.path;
|
||||
const request = toWebRequest(event);
|
||||
|
||||
if (pathname.startsWith("/api") || pathname !== "/") {
|
||||
const res = await handle(request);
|
||||
|
||||
if (res && res.status !== 404) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
<Callout type="success">
|
||||
You can visit https://localhost:3000/admin to see the admin UI. Additionally you can create more todos as you explore the admin UI.
|
||||
</Callout>
|
||||
|
||||
Create a helper file to instantiate the bknd instance and retrieve the API, importing the configuration from the `bknd.config.ts` file:
|
||||
|
||||
```ts title="server/utils/bknd.ts"
|
||||
import { type NuxtBkndConfig, getApp as getNuxtApp } from "bknd/adapter/nuxt";
|
||||
import bkndConfig from "../../bknd.config";
|
||||
|
||||
export async function getApp<Env = NodeJS.ProcessEnv>(
|
||||
config: NuxtBkndConfig<Env>,
|
||||
args: Env = process.env as Env,
|
||||
) {
|
||||
return await getNuxtApp(config, args);
|
||||
}
|
||||
|
||||
export async function getApi({ headers, verify }: { verify?: boolean; headers?: Headers }) {
|
||||
const app = await getApp(bkndConfig, process.env);
|
||||
|
||||
if (verify) {
|
||||
const api = app.getApi({ headers });
|
||||
await api.verifyAuth();
|
||||
return api;
|
||||
}
|
||||
|
||||
return app.getApi();
|
||||
};
|
||||
```
|
||||
|
||||
<Callout type="info">
|
||||
The adapter uses `process.env` to access environment variables, this works because Nuxt uses Nitro underneath and it will use polyfills for `process.env` making it platform/runtime agnostic.
|
||||
</Callout>
|
||||
|
||||
|
||||
## Example usage of the API
|
||||
|
||||
You can use the `getApp` function to access the bknd API in your app to expose endpoints,
|
||||
Here are some examples:
|
||||
|
||||
```typescript title="server/routes/todos.post.ts"
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event);
|
||||
const { data, action } = body;
|
||||
|
||||
const api = await getApi({});
|
||||
|
||||
switch (action) {
|
||||
case 'get': {
|
||||
const limit = 5;
|
||||
const todos = await api.data.readMany("todos", { limit, sort: "-id" });
|
||||
return { total: todos.body.meta.total, todos, limit };
|
||||
}
|
||||
|
||||
case 'create': {
|
||||
return await api.data.createOne("todos", { title: data.title });
|
||||
}
|
||||
|
||||
case 'delete': {
|
||||
return await api.data.deleteOne("todos", data.id);
|
||||
}
|
||||
|
||||
case 'toggle': {
|
||||
return await api.data.updateOne("todos", data.id, { done: !data.done });
|
||||
}
|
||||
|
||||
default: {
|
||||
return { path: action };
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
<Callout type="warning">
|
||||
This can't be done in the `server/api` directory as it will collide with the API endpoints created by the middleware. We will use [defineEventHandler](https://nuxt.com/docs/4.x/directory-structure/server) in the [server/routes](https://nuxt.com/docs/4.x/directory-structure/server) directory to create endpoints and use them in conjunction with composables to access the API safely.
|
||||
</Callout>
|
||||
|
||||
|
||||
### Using the API through composables
|
||||
|
||||
To use the API in your frontend components/pages, you can create a composable that uses the enpoints created in previous steps:
|
||||
|
||||
```typescript title="app/composables/useTodoActions.ts"
|
||||
import type { DB } from "bknd";
|
||||
|
||||
type Todo = DB["todos"];
|
||||
|
||||
export const useTodoActions = () => {
|
||||
const fetchTodos = () =>
|
||||
$fetch<{ limit: number; todos: Array<Todo>; total: number }>("/todos", {
|
||||
method: "POST",
|
||||
body: { action: "get" },
|
||||
});
|
||||
|
||||
const createTodo = (title: string) =>
|
||||
$fetch("/todos", {
|
||||
method: "POST",
|
||||
body: { action: "create", data: { title } },
|
||||
});
|
||||
|
||||
const deleteTodo = (todo: Todo) =>
|
||||
$fetch("/todos", {
|
||||
method: "POST",
|
||||
body: { action: "delete", data: { id: todo.id } },
|
||||
});
|
||||
|
||||
const toggleTodo = (todo: Todo) =>
|
||||
$fetch("/todos", {
|
||||
method: "POST",
|
||||
body: { action: "toggle", data: todo },
|
||||
});
|
||||
|
||||
return { fetchTodos, createTodo, deleteTodo, toggleTodo };
|
||||
};
|
||||
```
|
||||
|
||||
### Usage in a page/component
|
||||
|
||||
Make a composable to fetch the todos:
|
||||
|
||||
```ts title="app/composables/useTodoActions.ts"
|
||||
import type { DB } from "bknd";
|
||||
|
||||
type Todo = DB["todos"];
|
||||
|
||||
export const useTodoActions = () => {
|
||||
const fetchTodos = () =>
|
||||
$fetch<{ limit: number; todos: Array<Todo>; total: number }>("/todos", {
|
||||
method: "POST",
|
||||
body: { action: "get" },
|
||||
});
|
||||
|
||||
const createTodo = (title: string) =>
|
||||
$fetch("/todos", {
|
||||
method: "POST",
|
||||
body: { action: "create", data: { title } },
|
||||
});
|
||||
|
||||
const deleteTodo = (todo: Todo) =>
|
||||
$fetch("/todos", {
|
||||
method: "POST",
|
||||
body: { action: "delete", data: { id: todo.id } },
|
||||
});
|
||||
|
||||
const toggleTodo = (todo: Todo) =>
|
||||
$fetch("/todos", {
|
||||
method: "POST",
|
||||
body: { action: "toggle", data: todo },
|
||||
});
|
||||
|
||||
return { fetchTodos, createTodo, deleteTodo, toggleTodo };
|
||||
};
|
||||
```
|
||||
Then use the `useTodoActions` composable in a page:
|
||||
|
||||
```vue title="app/pages/todos.vue"
|
||||
<script lang="ts" setup>
|
||||
const { fetchTodos, createTodo, deleteTodo, toggleTodo } = useTodoActions();
|
||||
const { data:todos, execute } = await useAsyncData("todos", () => fetchTodos());
|
||||
|
||||
onMounted(() => {
|
||||
execute();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="todos"
|
||||
className="flex flex-col items-center justify-center min-h-screen p-8 pb-20 gap-16 sm:p-20"
|
||||
>
|
||||
<main className="flex flex-col gap-8 row-start-2 justify-center items-center sm:items-start">
|
||||
<div class="flex flex-row items-center ">
|
||||
<img class="dark:invert size-24" src="/nuxt.svg" alt="Nuxt logo" />
|
||||
<div class="ml-3.5 mr-2 font-mono opacity-70">&</div>
|
||||
<img class="dark:invert" src="/bknd.svg" alt="bknd logo" width="183" height="59" />
|
||||
</div>
|
||||
<div v-if="data?.todos">
|
||||
<ul>
|
||||
<li v-for="todo in data.todos" :key="todo.id">
|
||||
{{ todo.title }}
|
||||
<button @click="toggleTodo(todo)">Toggle</button>
|
||||
<button @click="deleteTodo(todo)">Delete</button>
|
||||
</li>
|
||||
</ul>
|
||||
<form @submit.prevent="createTodo('New Todo')">
|
||||
<input type="text" placeholder="New Todo" />
|
||||
<button type="submit">Add</button>
|
||||
</form>
|
||||
</div>
|
||||
<div v-else className="flex flex-col gap-1">
|
||||
<p>
|
||||
No todos found.
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
<Callout type="success">
|
||||
You can visit https://localhost:3000/todos to see all the todos.
|
||||
</Callout>
|
||||
|
||||
### Using authentication
|
||||
|
||||
Make a composable to fetch the user:
|
||||
|
||||
```ts title="app/composables/useUser.ts"
|
||||
import type { User } from "bknd";
|
||||
|
||||
export const useUser = () => {
|
||||
const getUser = () => $fetch("/api/auth/me") as Promise<{ user: User }>;
|
||||
return { getUser };
|
||||
};
|
||||
```
|
||||
|
||||
Then use the `useUser` composable in a page:
|
||||
|
||||
```vue title="app/pages/user.vue"
|
||||
<script lang="ts" setup>
|
||||
const { getUser } = useUser();
|
||||
const { data, status: userStatus, execute } = await useAsyncData("user", () => getUser());
|
||||
|
||||
onMounted(() => {
|
||||
execute();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="userStatus !== 'pending'"
|
||||
className="flex flex-col items-center justify-center min-h-screen p-8 pb-20 gap-16 sm:p-20"
|
||||
>
|
||||
<main className="flex flex-col gap-8 row-start-2 justify-center items-center sm:items-start">
|
||||
<div class="flex flex-row items-center ">
|
||||
<img class="dark:invert size-24" src="/nuxt.svg" alt="Nuxt logo" />
|
||||
<div class="ml-3.5 mr-2 font-mono opacity-70">&</div>
|
||||
<img class="dark:invert" src="/bknd.svg" alt="bknd logo" width="183" height="59" />
|
||||
</div>
|
||||
<div v-if="data?.user">
|
||||
Logged in as {{ data.user.email }}.
|
||||
<a className="font-medium underline" href='/api/auth/logout'>
|
||||
Logout
|
||||
</a>
|
||||
</div>
|
||||
<div v-else className="flex flex-col gap-1">
|
||||
<p>
|
||||
Not logged in.
|
||||
<a className="font-medium underline" href="/admin/auth/login">
|
||||
Login
|
||||
</a>
|
||||
</p>
|
||||
<p className="text-xs opacity-50">
|
||||
Sign in with:
|
||||
<b>
|
||||
<code>test@bknd.io</code>
|
||||
</b>
|
||||
/
|
||||
<b>
|
||||
<code>12345678</code>
|
||||
</b>
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Important Note
|
||||
|
||||
Use `external` attribute on Nuxt links, anytime you are traversing to an external route (like `/api/*` which are handled by bknd's middleware) to prevent vue router from intercepting the link.
|
||||
|
||||
```vue
|
||||
<NuxtLink external href="/admin">
|
||||
Admin
|
||||
</NuxtLink>
|
||||
```
|
||||
|
||||
<Callout type="error">
|
||||
If you don't use the `external` attribute, vue router will intercept the link and try to navigate to it, which will fail and result in a 404 error.
|
||||
</Callout>
|
||||
|
||||
Check the [Nuxt repository example](https://github.com/bknd-io/bknd/tree/main/examples/nuxt) for more implementation details.
|
||||
@@ -0,0 +1,334 @@
|
||||
---
|
||||
title: "Solid Start"
|
||||
description: "Run bknd inside Solid Start"
|
||||
tags: ["documentation"]
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
To get started with Solid Start and bknd you can either install the package manually, and follow the descriptions below, or use the CLI starter.
|
||||
|
||||
Create a new Solid Start project by following the [official guide](https://docs.solidjs.com/solid-start/getting-started), and then install bknd as a dependency:
|
||||
|
||||
<Tabs groupId='package-manager' persist items={[ 'npm', 'pnpm', 'yarn', 'bun']}>
|
||||
|
||||
```bash tab="npm"
|
||||
npm install bknd
|
||||
```
|
||||
|
||||
```bash tab="pnpm"
|
||||
pnpm install bknd
|
||||
```
|
||||
|
||||
```bash tab="yarn"
|
||||
yarn add bknd
|
||||
```
|
||||
|
||||
```bash tab="bun"
|
||||
bun add bknd
|
||||
```
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Configuration
|
||||
|
||||
<Callout type="warning">
|
||||
When run with Node.js, a version of 22 (LTS) or higher is required. Please
|
||||
verify your version by running `node -v`, and
|
||||
[upgrade](https://nodejs.org/en/download/) if necessary.
|
||||
</Callout>
|
||||
|
||||
Now create a `bknd.config.ts` file in the root of your project. If you created the project using the CLI starter, this file is already created for you.
|
||||
|
||||
```typescript title="bknd.config.ts"
|
||||
import type { SolidStartBkndConfig } from "bknd/adapter/solid-start";
|
||||
import { em, entity, text, boolean } from "bknd";
|
||||
import { secureRandomString } from "bknd/utils";
|
||||
|
||||
const schema = em({
|
||||
todos: entity("todos", {
|
||||
title: text(),
|
||||
done: boolean(),
|
||||
}),
|
||||
});
|
||||
|
||||
// register your schema to get automatic type completion
|
||||
type Database = (typeof schema)["DB"];
|
||||
declare module "bknd" {
|
||||
interface DB extends Database {}
|
||||
}
|
||||
|
||||
export default {
|
||||
connection: {
|
||||
url: "file:data.db",
|
||||
},
|
||||
options: {
|
||||
// the seed option is only executed if the database was empty
|
||||
seed: async (ctx) => {
|
||||
// create some entries
|
||||
await ctx.em.mutator("todos").insertMany([
|
||||
{ title: "Learn bknd", done: true },
|
||||
{ title: "Build something cool", done: false },
|
||||
]);
|
||||
|
||||
// and create a user
|
||||
await ctx.app.module.auth.createUser({
|
||||
email: "test@bknd.io",
|
||||
password: "12345678",
|
||||
});
|
||||
},
|
||||
},
|
||||
config: {
|
||||
data: schema.toJSON(),
|
||||
auth: {
|
||||
enabled: true,
|
||||
jwt: {
|
||||
secret: secureRandomString(32),
|
||||
},
|
||||
},
|
||||
},
|
||||
// please run `bun bknd copy-assets --out public/admin` to copy the admin assets into the public directory
|
||||
// alternatively add this as a postinstall script in your package.json like this:
|
||||
// "postinstall": "bknd copy-assets --out public/admin"
|
||||
adminOptions: {
|
||||
adminBasepath: "/admin",
|
||||
assetsPath: "/admin/", // trailing slash is important
|
||||
},
|
||||
} satisfies SolidStartBkndConfig;
|
||||
```
|
||||
|
||||
See [bknd.config.ts](/extending/config) for more information on how to configure bknd. The `SolidStartBkndConfig` type extends the `BkndConfig` type with the following additional properties:
|
||||
|
||||
```typescript
|
||||
export type SolidStartBkndConfig<Env = SolidStartEnv> = RuntimeBkndConfig<Env>;
|
||||
```
|
||||
|
||||
## Serve the API and Admin UI
|
||||
|
||||
The Solid Start adapter uses middleware to handle API requests and serve the Admin UI. Create a `src/middleware/index.ts` file:
|
||||
|
||||
```ts title="src/middleware/index.ts"
|
||||
import { createMiddleware } from "@solidjs/start/middleware";
|
||||
import config from "../../bknd.config";
|
||||
import { serve } from "bknd/adapter/solid-start";
|
||||
|
||||
const handler = serve(config);
|
||||
|
||||
export default createMiddleware({
|
||||
onRequest: async (event) => {
|
||||
const url = new URL(event.request.url);
|
||||
const pathname = url.pathname;
|
||||
|
||||
if (pathname.startsWith("/api") || pathname !== "/") {
|
||||
const res = await handler(event.request);
|
||||
|
||||
if (res && res.status !== 404) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Update the app config (Solid Start v1 based on vinxi)
|
||||
|
||||
Add the following to your `app.config.ts` file:
|
||||
|
||||
```ts title="app.config.ts"
|
||||
import { defineConfig } from "@solidjs/start/config";
|
||||
|
||||
const app = defineConfig({
|
||||
// ... your existing config
|
||||
middleware: "src/middleware/index.ts",
|
||||
// ... your existing config
|
||||
});
|
||||
|
||||
export default app;
|
||||
```
|
||||
|
||||
### Update the vite config (Solid Start v2)
|
||||
|
||||
Add the following to your `vite.config.ts` file:
|
||||
|
||||
```ts title="vite.config.ts"
|
||||
import { defineConfig } from "vite";
|
||||
import solid from "vite-plugin-solid";
|
||||
import { solidStart } from "@solidjs/start/vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
solid(),
|
||||
solidStart({
|
||||
middleware: "./src/middleware/index.ts",
|
||||
}),
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
<Callout type="success">
|
||||
You can visit https://localhost:3000/admin to see the admin UI. Additionally you can create more todos as you explore the admin UI.
|
||||
</Callout>
|
||||
|
||||
Create a helper file to instantiate the bknd instance and retrieve the API, importing the configuration from the `bknd.config.ts` file:
|
||||
|
||||
|
||||
```ts title="src/lib/bknd.ts"
|
||||
import { getApp as getBkndApp } from "bknd/adapter/solid-start";
|
||||
import bkndConfig from "../../bknd.config";
|
||||
import type { App } from "bknd";
|
||||
|
||||
export const getApp = async () => {
|
||||
return await getBkndApp(bkndConfig);
|
||||
};
|
||||
|
||||
export async function getApi({
|
||||
headers,
|
||||
verify,
|
||||
}: {
|
||||
verify?: boolean;
|
||||
headers?: Headers;
|
||||
}) {
|
||||
const app = await getApp();
|
||||
|
||||
if (verify) {
|
||||
const api = app.getApi({ headers });
|
||||
await api.verifyAuth();
|
||||
return api;
|
||||
}
|
||||
|
||||
return app.getApi();
|
||||
};
|
||||
```
|
||||
## Example usage of the API
|
||||
|
||||
You can use the `getApi` helper function we've already set up to fetch and mutate in static pages and server components:
|
||||
|
||||
```tsx title="src/routes/index.tsx"
|
||||
import { getApi } from "~/lib/bknd";
|
||||
import { query, createAsync } from "@solidjs/router";
|
||||
|
||||
export const getTodo = async () => {
|
||||
"use server"
|
||||
const api = await getApi({});
|
||||
const limit = 5;
|
||||
const todos = await api.data.readMany("todos");
|
||||
const total = todos.body.meta.total as number;
|
||||
return { total, todos: todos as unknown as Todo[], limit };
|
||||
};
|
||||
|
||||
const getTodosFromServer = query(async () => await getTodo(), "getTodosFromServer");
|
||||
|
||||
export default function Home() {
|
||||
const data = createAsync(() => getTodosFromServer());
|
||||
|
||||
return (
|
||||
<ul>
|
||||
<For each={data()}>
|
||||
{(todo) => <li>{todo.title}</li>}
|
||||
</For>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Using authentication
|
||||
|
||||
When using authentication, you'll need to pass the headers to the `getApi` function to verify the user is authenticated.
|
||||
|
||||
```tsx title="src/routes/auth.tsx"
|
||||
import { createResource, Suspense } from "solid-js";
|
||||
import { A } from "@solidjs/router";
|
||||
import { getRequestEvent } from "solid-js/web";
|
||||
import { getApi } from "~/lib/bknd";
|
||||
|
||||
export const getUser = async () => {
|
||||
"use server"
|
||||
const request = getRequestEvent()?.request;
|
||||
const api = await getApi({ verify: true, headers: request?.headers });
|
||||
return api.getUser();
|
||||
}
|
||||
|
||||
export default function Auth() {
|
||||
const [data] = createResource(async () => {
|
||||
const user = await getUser()
|
||||
return { user };
|
||||
}, {
|
||||
initialValue: {
|
||||
user: null
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div class="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
|
||||
<main class="flex flex-col gap-8 row-start-2 items-center sm:items-start">
|
||||
<div class="flex flex-row items-center ">
|
||||
<img
|
||||
class="dark:invert size-18"
|
||||
src="/solid.svg"
|
||||
alt="Solid logo"
|
||||
/>
|
||||
<div class="ml-3.5 mr-2 opacity-70">&</div>
|
||||
<img
|
||||
class="dark:invert"
|
||||
src="/bknd.svg"
|
||||
alt="bknd logo"
|
||||
width={183}
|
||||
height={59}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Suspense fallback={<p>Loading...</p>}>
|
||||
<div>
|
||||
{data()?.user ? (
|
||||
<>
|
||||
Logged in as {data()?.user?.email}.
|
||||
<A
|
||||
class="underline"
|
||||
target="_self"
|
||||
href={"/api/auth/logout"}
|
||||
>
|
||||
Logout
|
||||
</A>
|
||||
</>
|
||||
) : (
|
||||
<div class="flex flex-col gap-1">
|
||||
<p>
|
||||
Not logged in.
|
||||
<A
|
||||
class="underline"
|
||||
target="_self"
|
||||
href={"/admin/auth/login"}
|
||||
>
|
||||
Login
|
||||
</A>
|
||||
</p>
|
||||
<p class="text-xs opacity-50">
|
||||
Sign in with:
|
||||
<b>
|
||||
<code>test@bknd.io</code>
|
||||
</b>
|
||||
/
|
||||
<b>
|
||||
<code>12345678</code>
|
||||
</b>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Suspense>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Tips
|
||||
|
||||
Use `target="_self"` on links, anytime your traversing to an external route (like `/api/auth/logout` which are handled by bknd's middleware) to prevent solid router from intercepting the link.
|
||||
```jsx
|
||||
<A href="/api/auth/logout" target="_self">
|
||||
Logout
|
||||
</A>
|
||||
```
|
||||
@@ -39,6 +39,18 @@ bknd seamlessly integrates with popular frameworks, allowing you to use what you
|
||||
href="/integration/tanstack-start"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="simple-icons:nuxt" className="text-fd-primary !size-6" />}
|
||||
title="Nuxt"
|
||||
href="/integration/nuxt"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="simple-icons:solid" className="text-fd-primary !size-6" />}
|
||||
title="Solid Start"
|
||||
href="/integration/solid-start"
|
||||
/>
|
||||
|
||||
<Card title="Yours missing?" href="https://github.com/bknd-io/bknd/issues/new">
|
||||
Create a new issue to request a guide for your framework.
|
||||
</Card>
|
||||
|
||||
@@ -156,6 +156,18 @@ Pick your framework or runtime to get started.
|
||||
href="/integration/tanstack-start"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="simple-icons:nuxt" className="text-fd-primary !size-6" />}
|
||||
title="Nuxt"
|
||||
href="/integration/nuxt"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="simple-icons:solid" className="text-fd-primary !size-6" />}
|
||||
title="Solid Start"
|
||||
href="/integration/solid-start"
|
||||
/>
|
||||
|
||||
<Card
|
||||
icon={<Icon icon="tabler:lambda" className="text-fd-primary !size-6" />}
|
||||
title="AWS Lambda"
|
||||
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"target": "ESNext",
|
||||
"target": "es2023",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
|
||||
Reference in New Issue
Block a user