|
|
|
@@ -0,0 +1,335 @@
|
|
|
|
|
---
|
|
|
|
|
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 async function Home() {
|
|
|
|
|
const api = await getApi();
|
|
|
|
|
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 Home() {
|
|
|
|
|
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>
|
|
|
|
|
```
|