improved astro adapter (serving api) + added documentation

This commit is contained in:
dswbx
2024-12-01 08:58:08 +01:00
parent b55fdd7516
commit feeb13c053
9 changed files with 166 additions and 23 deletions

View File

@@ -1,10 +1,8 @@
import { Api, type ApiOptions } from "bknd"; import { Api, type ApiOptions } from "bknd";
import { App, type CreateAppConfig } from "bknd";
type TAstro = { type TAstro = {
request: { request: Request;
url: string;
headers: Headers;
};
}; };
export type Options = { export type Options = {
@@ -19,3 +17,15 @@ export function getApi(Astro: TAstro, options: Options = { mode: "static" }) {
headers: options.mode === "dynamic" ? Astro.request.headers : undefined headers: options.mode === "dynamic" ? Astro.request.headers : undefined
}); });
} }
let app: App;
export function serve(config: CreateAppConfig) {
return async (args: TAstro) => {
if (!app) {
app = App.create(config);
await app.build();
}
return app.fetch(args.request);
};
}

View File

@@ -11,10 +11,21 @@ export class AuthController implements ClassController {
getMiddleware: MiddlewareHandler = async (c, next) => { getMiddleware: MiddlewareHandler = async (c, next) => {
// @todo: ONLY HOTFIX // @todo: ONLY HOTFIX
// middlewares are added for all routes are registered. But we need to make sure that
// only HTML/JSON routes are adding a cookie to the response. Config updates might
// also use an extension "syntax", e.g. /api/system/patch/data/entities.posts
// This middleware should be extracted and added by each Controller individually,
// but it requires access to the auth secret.
// Note: This doesn't mean endpoints aren't protected, just the cookie is not set.
const url = new URL(c.req.url); const url = new URL(c.req.url);
const last = url.pathname.split("/")?.pop(); const last = url.pathname.split("/")?.pop();
const ext = last?.includes(".") ? last.split(".")?.pop() : undefined; const ext = last?.includes(".") ? last.split(".")?.pop() : undefined;
if (ext) { if (
!this.auth.authenticator.isJsonRequest(c) &&
["GET", "HEAD", "OPTIONS"].includes(c.req.method) &&
ext &&
["js", "css", "png", "jpg", "jpeg", "svg", "ico"].includes(ext)
) {
isDebug() && console.log("Skipping auth", { ext }, url.pathname); isDebug() && console.log("Skipping auth", { ext }, url.pathname);
} else { } else {
const user = await this.auth.authenticator.resolveAuthFromRequest(c); const user = await this.auth.authenticator.resolveAuthFromRequest(c);

View File

@@ -249,6 +249,7 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
} }
} }
// @todo: move this to a server helper
isJsonRequest(c: Context): boolean { isJsonRequest(c: Context): boolean {
//return c.req.header("Content-Type") === "application/x-www-form-urlencoded"; //return c.req.header("Content-Type") === "application/x-www-form-urlencoded";
return c.req.header("Content-Type") === "application/json"; return c.req.header("Content-Type") === "application/json";

BIN
bun.lockb

Binary file not shown.

120
docs/integration/astro.mdx Normal file
View File

@@ -0,0 +1,120 @@
---
title: 'Astro'
description: 'Run bknd inside Astro'
---
import InstallBknd from '/snippets/install-bknd.mdx';
## Installation
Install bknd as a dependency:
<InstallBknd />
For the Astro integration to work, you also need to [add the react integration](https://docs.astro.build/en/guides/integrations-guide/react/):
```bash
npx astro add react
```
You also need to make sure to set the output to `hybrid` in your Astro config:
```js {6}
// astro.config.mjs
import { defineConfig } from "astro/config";
import react from "@astrojs/react";
export default defineConfig({
output: "hybrid",
integrations: [react()]
});
```
<Note>
If you don't want to use React with Astro, there is also an option to serve the bknd Admin UI
statically using Astro's middleware. In case you're interested in this, feel free to reach
out in [Discord](https://discord.gg/952SFk8Tb8) or open an [issue on GitHub](https://github.com/bknd-io/bknd/issues/new).
</Note>
## Serve the API
Create a new catch-all route at `src/pages/api/[...api].ts`:
```ts src/pages/api/[...api].ts
import { serve } from "bknd/adapter/astro";
export const prerender = false;
export const ALL = serve({
connection: {
type: "libsql",
config: {
url: "http://127.0.0.1:8080"
}
}
});
```
For more information about the connection object, refer to the [Setup](/setup) guide. In the
special case of astro, you may also use your Astro DB credentials since it's also using LibSQL
under the hood. Refer to the [Astro DB documentation](https://docs.astro.build/en/guides/astro-db/) for more information.
## Enabling the Admin UI
Create a new catch-all route at `src/pages/admin/[...admin].astro`:
```jsx src/pages/admin/[...admin].astro
---
import { Admin } from "bknd/ui";
import "bknd/dist/styles.css";
import { getApi } from "bknd/adapter/astro";
const api = getApi(Astro, { mode: "dynamic" });
const user = api.getUser();
export const prerender = false;
---
<html>
<body>
<Admin
withProvider={{ user }}
config={{ basepath: "/admin", color_scheme: "dark" }}
client:load
/>
</body>
</html>
```
## Example usage of the API
You use the API in both static and SSR pages. Just note that on static pages, authentication
might not work as expected, because Cookies are not available in the static context.
Here is an example of using the API in static context:
```jsx
---
import { getApi } from "bknd/adapter/astro";
const api = getApi(Astro);
const { data } = await api.data.readMany("todos");
---
<ul>
{data.map((todo) => (
<li>{todo.title}</li>
))}
</ul>
```
On SSR pages, you can also access the authenticated user:
```jsx
---
import { getApi } from "bknd/adapter/astro";
const api = getApi(Astro, { mode: "dynamic" });
const user = api.getUser();
const { data } = await api.data.readMany("todos");
export const prerender = false;
---
{user
? <p>Logged in as <b>{user.email}</b>.</p>
: <p>Not authenticated.</p>}
<ul>
{data.map((todo) => (
<li>{todo.title}</li>
))}
</ul>
```
Check the [astro example](https://github.com/bknd-io/bknd/tree/main/examples/astro) for more implementation details.

View File

@@ -45,6 +45,15 @@ in the future, so stay tuned!
17.424c.18 2.31.18 3.394.18 4.576h-5.35c0-.258.004-.493.009-.732c.014-.743.03-1.517-.09-3.081c-.16-2.29-1.147-2.799-2.961-2.799H3.305v-4.166h8.67c2.291 0 3.437-.696 3.437-2.54c0-1.623-1.146-2.605-3.437-2.605h-8.67V2h9.624c5.189 0 7.767 2.449 7.767 6.36c0 2.926-1.814 4.834-4.265 5.152c2.069.413 3.278 1.59 3.501 3.912" clip-rule="evenodd"/><path fill="currentColor" d="M3.305 22v-3.106h5.657c.945 0 1.15.7 1.15 1.118V22z"/></svg> 17.424c.18 2.31.18 3.394.18 4.576h-5.35c0-.258.004-.493.009-.732c.014-.743.03-1.517-.09-3.081c-.16-2.29-1.147-2.799-2.961-2.799H3.305v-4.166h8.67c2.291 0 3.437-.696 3.437-2.54c0-1.623-1.146-2.605-3.437-2.605h-8.67V2h9.624c5.189 0 7.767 2.449 7.767 6.36c0 2.926-1.814 4.834-4.265 5.152c2.069.413 3.278 1.59 3.501 3.912" clip-rule="evenodd"/><path fill="currentColor" d="M3.305 22v-3.106h5.657c.945 0 1.15.7 1.15 1.118V22z"/></svg>
</div>} </div>}
href="/integration/remix" href="/integration/remix"
/>
<Card
title="Astro"
icon={<div className="text-primary-light">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<rect width="24" height="24" fill="none"/><path fill="currentColor" d="M9.24 19.035c-.901-.826-1.164-2.561-.789-3.819c.65.793 1.552 1.044 2.486 1.186c1.44.218 2.856.137 4.195-.524c.153-.076.295-.177.462-.278c.126.365.159.734.115 1.11c-.107.915-.56 1.622-1.283 2.158c-.289.215-.594.406-.892.608c-.916.622-1.164 1.35-.82 2.41l.034.114a2.4 2.4 0 0 1-1.07-.918a2.6 2.6 0 0 1-.412-1.401c-.003-.248-.003-.497-.036-.74c-.081-.595-.36-.86-.883-.876a1.034 1.034 0 0 0-1.075.843q-.013.058-.033.126M4.1 15.007s2.666-1.303 5.34-1.303l2.016-6.26c.075-.304.296-.51.544-.51c.25 0 .47.206.545.51l2.016 6.26c3.167 0 5.34 1.303 5.34 1.303L15.363 2.602c-.13-.366-.35-.602-.645-.602H9.283c-.296 0-.506.236-.645.602c-.01.024-4.538 12.405-4.538 12.405"/>
</svg>
</div>}
href="/integration/astro"
/> />
<Card <Card
title="Cloudflare" title="Cloudflare"

View File

@@ -108,6 +108,7 @@
"integration/bun", "integration/bun",
"integration/vite", "integration/vite",
"integration/express", "integration/express",
"integration/astro",
"integration/nodejs", "integration/nodejs",
"integration/deno", "integration/deno",
"integration/browser" "integration/browser"

View File

@@ -5,6 +5,6 @@
"dev": "mintlify dev" "dev": "mintlify dev"
}, },
"devDependencies": { "devDependencies": {
"mintlify": "^4.0.269" "mintlify": "^4.0.285"
} }
} }

View File

@@ -1,21 +1,12 @@
import type { APIRoute } from "astro"; import { serve } from "bknd/adapter/astro";
import { App } from "bknd";
export const prerender = false; export const prerender = false;
let app: App; export const ALL = serve({
export const ALL: APIRoute = async ({ request }) => {
if (!app) {
app = App.create({
connection: { connection: {
type: "libsql", type: "libsql",
config: { config: {
url: "http://127.0.0.1:8080" url: "http://127.0.0.1:8080"
} }
} }
}); });
await app.build();
}
return app.fetch(request);
};