From 7751ee5db889a4ce00b8b0c40e36c18668a94c50 Mon Sep 17 00:00:00 2001 From: Shishant Biswas Date: Mon, 9 Mar 2026 19:05:31 +0530 Subject: [PATCH] init: nuxt adapter --- app/build.ts | 5 + app/e2e/inc/adapters.ts | 3 + app/package.json | 7 + app/src/adapter/nuxt/index.ts | 1 + app/src/adapter/nuxt/nuxt.adapter.spec.ts | 15 + app/src/adapter/nuxt/nuxt.adapter.ts | 30 ++ .../integration/(frameworks)/meta.json | 3 +- .../integration/(frameworks)/nuxt.mdx | 388 ++++++++++++++++++ .../integration/introduction.mdx | 6 + docs/content/docs/(documentation)/start.mdx | 6 + examples/nuxt/.gitignore | 27 ++ examples/nuxt/README.md | 64 +++ examples/nuxt/app/assets/css/main.css | 24 ++ examples/nuxt/app/components/Buttons.vue | 13 + examples/nuxt/app/components/Footer.vue | 29 ++ examples/nuxt/app/components/List.vue | 21 + .../nuxt/app/composables/useTodoActions.ts | 31 ++ examples/nuxt/app/composables/useUser.ts | 6 + examples/nuxt/app/pages/index.vue | 64 +++ examples/nuxt/app/pages/user.vue | 49 +++ examples/nuxt/bknd.config.ts | 58 +++ examples/nuxt/nuxt.config.ts | 11 + examples/nuxt/package.json | 21 + examples/nuxt/public/bknd.ico | Bin 0 -> 15086 bytes examples/nuxt/public/bknd.svg | 14 + examples/nuxt/public/favicon.ico | Bin 0 -> 4286 bytes examples/nuxt/public/file.svg | 1 + examples/nuxt/public/globe.svg | 1 + examples/nuxt/public/nuxt.svg | 3 + examples/nuxt/public/robots.txt | 2 + examples/nuxt/public/window.svg | 1 + examples/nuxt/server/middleware/bknd.ts | 15 + examples/nuxt/server/routes/todos.post.ts | 31 ++ examples/nuxt/server/utils/bknd.ts | 21 + examples/nuxt/tsconfig.json | 18 + 35 files changed, 988 insertions(+), 1 deletion(-) create mode 100644 app/src/adapter/nuxt/index.ts create mode 100644 app/src/adapter/nuxt/nuxt.adapter.spec.ts create mode 100644 app/src/adapter/nuxt/nuxt.adapter.ts create mode 100644 docs/content/docs/(documentation)/integration/(frameworks)/nuxt.mdx create mode 100644 examples/nuxt/.gitignore create mode 100644 examples/nuxt/README.md create mode 100644 examples/nuxt/app/assets/css/main.css create mode 100644 examples/nuxt/app/components/Buttons.vue create mode 100644 examples/nuxt/app/components/Footer.vue create mode 100644 examples/nuxt/app/components/List.vue create mode 100644 examples/nuxt/app/composables/useTodoActions.ts create mode 100644 examples/nuxt/app/composables/useUser.ts create mode 100644 examples/nuxt/app/pages/index.vue create mode 100644 examples/nuxt/app/pages/user.vue create mode 100644 examples/nuxt/bknd.config.ts create mode 100644 examples/nuxt/nuxt.config.ts create mode 100644 examples/nuxt/package.json create mode 100644 examples/nuxt/public/bknd.ico create mode 100644 examples/nuxt/public/bknd.svg create mode 100644 examples/nuxt/public/favicon.ico create mode 100644 examples/nuxt/public/file.svg create mode 100644 examples/nuxt/public/globe.svg create mode 100644 examples/nuxt/public/nuxt.svg create mode 100644 examples/nuxt/public/robots.txt create mode 100644 examples/nuxt/public/window.svg create mode 100644 examples/nuxt/server/middleware/bknd.ts create mode 100644 examples/nuxt/server/routes/todos.post.ts create mode 100644 examples/nuxt/server/utils/bknd.ts create mode 100644 examples/nuxt/tsconfig.json diff --git a/app/build.ts b/app/build.ts index 1ab90ea..92f6335 100644 --- a/app/build.ts +++ b/app/build.ts @@ -335,6 +335,11 @@ async function buildAdapters() { platform: "node", }), + tsup.build({ + ...baseConfig("nuxt"), + platform: "node", + }), + tsup.build({ ...baseConfig("node"), platform: "node", diff --git a/app/e2e/inc/adapters.ts b/app/e2e/inc/adapters.ts index 30b647b..66d691b 100644 --- a/app/e2e/inc/adapters.ts +++ b/app/e2e/inc/adapters.ts @@ -15,6 +15,9 @@ const configs = { nextjs: { base_path: "/admin", }, + nuxt: { + base_path: "/admin", + }, astro: { base_path: "/admin", }, diff --git a/app/package.json b/app/package.json index ceb3771..dd8d457 100644 --- a/app/package.json +++ b/app/package.json @@ -233,6 +233,11 @@ "import": "./dist/adapter/nextjs/index.js", "require": "./dist/adapter/nextjs/index.js" }, + "./adapter/nuxt": { + "types": "./dist/types/adapter/nuxt/index.d.ts", + "import": "./dist/adapter/nuxt/index.js", + "require": "./dist/adapter/nuxt/index.js" + }, "./adapter/react-router": { "types": "./dist/types/adapter/react-router/index.d.ts", "import": "./dist/adapter/react-router/index.js", @@ -287,6 +292,7 @@ "adapter/cloudflare": ["./dist/types/adapter/cloudflare/index.d.ts"], "adapter/vite": ["./dist/types/adapter/vite/index.d.ts"], "adapter/nextjs": ["./dist/types/adapter/nextjs/index.d.ts"], + "adapter/nuxt": ["./dist/types/adapter/nuxt/index.d.ts"], "adapter/react-router": ["./dist/types/adapter/react-router/index.d.ts"], "adapter/bun": ["./dist/types/adapter/bun/index.d.ts"], "adapter/node": ["./dist/types/adapter/node/index.d.ts"], @@ -318,6 +324,7 @@ "serverless", "cloudflare", "nextjs", + "nuxt", "remix", "react-router", "astro", diff --git a/app/src/adapter/nuxt/index.ts b/app/src/adapter/nuxt/index.ts new file mode 100644 index 0000000..c6920bb --- /dev/null +++ b/app/src/adapter/nuxt/index.ts @@ -0,0 +1 @@ +export * from "./nuxt.adapter"; diff --git a/app/src/adapter/nuxt/nuxt.adapter.spec.ts b/app/src/adapter/nuxt/nuxt.adapter.spec.ts new file mode 100644 index 0000000..409771d --- /dev/null +++ b/app/src/adapter/nuxt/nuxt.adapter.spec.ts @@ -0,0 +1,15 @@ +import { afterAll, beforeAll, describe } from "bun:test"; +import * as nuxt from "./nuxt.adapter"; +import { disableConsoleLog, enableConsoleLog } from "core/utils"; +import { adapterTestSuite } from "adapter/adapter-test-suite"; +import { bunTestRunner } from "adapter/bun/test"; + +beforeAll(disableConsoleLog); +afterAll(enableConsoleLog); + +describe("nuxt adapter", () => { + adapterTestSuite(bunTestRunner, { + makeApp: nuxt.getApp, + makeHandler: nuxt.serve, + }); +}); diff --git a/app/src/adapter/nuxt/nuxt.adapter.ts b/app/src/adapter/nuxt/nuxt.adapter.ts new file mode 100644 index 0000000..426c735 --- /dev/null +++ b/app/src/adapter/nuxt/nuxt.adapter.ts @@ -0,0 +1,30 @@ +import { createRuntimeApp, type RuntimeBkndConfig } from "bknd/adapter"; + +export type NuxtEnv = NodeJS.ProcessEnv; +export type NuxtBkndConfig = RuntimeBkndConfig; + +/** + * Get bknd app instance + * @param config - bknd configuration + * @param args - environment variables + */ +export async function getApp( + config: NuxtBkndConfig = {} as NuxtBkndConfig, + args: Env, +) { + return await createRuntimeApp(config, args); +} + +/** + * Create middleware handler for Nuxt + * @param config - bknd configuration + * @param args - environment variables + */ +export function serve( + config: NuxtBkndConfig = {} as NuxtBkndConfig, + args: Env, +) { + return async (request: Request) => { + return (await getApp(config, args)).fetch(request); + }; +} diff --git a/docs/content/docs/(documentation)/integration/(frameworks)/meta.json b/docs/content/docs/(documentation)/integration/(frameworks)/meta.json index 5cc7900..39b253c 100644 --- a/docs/content/docs/(documentation)/integration/(frameworks)/meta.json +++ b/docs/content/docs/(documentation)/integration/(frameworks)/meta.json @@ -5,6 +5,7 @@ "astro", "sveltekit", "tanstack-start", - "vite" + "vite", + "nuxt" ] } diff --git a/docs/content/docs/(documentation)/integration/(frameworks)/nuxt.mdx b/docs/content/docs/(documentation)/integration/(frameworks)/nuxt.mdx new file mode 100644 index 0000000..3cc715d --- /dev/null +++ b/docs/content/docs/(documentation)/integration/(frameworks)/nuxt.mdx @@ -0,0 +1,388 @@ +--- +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: + + + +```bash tab="npm" +npm install bknd +``` + +```bash tab="pnpm" +pnpm install bknd +``` + +```bash tab="yarn" +yarn add bknd +``` + +```bash tab="bun" +bun add bknd +``` + + + +## Configuration + + + 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. + + +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 = FrameworkBkndConfig; +``` + +## 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; + } + } +}); +``` + + + You can visit https://localhost:3000/admin to see the admin UI. Additionally you can create more todos as you explore the admin UI. + + +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( + config: NuxtBkndConfig, + 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(); +}; +``` + + + 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. + + + +## 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 }; + } + } +}); +``` + + + 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. + + + +### 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; 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; 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" + + + +``` + + + You can visit https://localhost:3000/todos to see all the todos. + + +### 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" + + + +``` + +Check the [Nuxt repository example](https://github.com/bknd-io/bknd/tree/main/examples/nuxt) for more implementation details. \ No newline at end of file diff --git a/docs/content/docs/(documentation)/integration/introduction.mdx b/docs/content/docs/(documentation)/integration/introduction.mdx index 6b94374..a04cb12 100644 --- a/docs/content/docs/(documentation)/integration/introduction.mdx +++ b/docs/content/docs/(documentation)/integration/introduction.mdx @@ -39,6 +39,12 @@ bknd seamlessly integrates with popular frameworks, allowing you to use what you href="/integration/tanstack-start" /> +} + title="Nuxt" + href="/integration/nuxt" +/> + Create a new issue to request a guide for your framework. diff --git a/docs/content/docs/(documentation)/start.mdx b/docs/content/docs/(documentation)/start.mdx index 744588d..d0644ae 100644 --- a/docs/content/docs/(documentation)/start.mdx +++ b/docs/content/docs/(documentation)/start.mdx @@ -156,6 +156,12 @@ Pick your framework or runtime to get started. href="/integration/tanstack-start" /> +} + title="Nuxt" + href="/integration/nuxt" +/> + } title="AWS Lambda" diff --git a/examples/nuxt/.gitignore b/examples/nuxt/.gitignore new file mode 100644 index 0000000..8cd13a3 --- /dev/null +++ b/examples/nuxt/.gitignore @@ -0,0 +1,27 @@ +# Nuxt dev/build outputs +.output +.data +.nuxt +.nitro +.cache +dist + +# Node dependencies +node_modules + +# Logs +logs +*.log + +# Misc +.DS_Store +.fleet +.idea + +# Local env files +.env +.env.* +!.env.example + +public/admin +data.db \ No newline at end of file diff --git a/examples/nuxt/README.md b/examples/nuxt/README.md new file mode 100644 index 0000000..25990b9 --- /dev/null +++ b/examples/nuxt/README.md @@ -0,0 +1,64 @@ +# bknd starter: Nuxt +A minimal example of a Nuxt project with bknd integration. + +## Project Structure + +Inside of your Nuxt project, you'll see the following folders and files: + +```text +. +├── app +│ ├── assets +│ │ └── css +│ ├── components +│ │ ├── Buttons.vue +│ │ ├── Footer.vue +│ │ └── List.vue +│ ├── composables +│ │ ├── useTodoActions.ts +│ │ └── useUser.ts +│ └── pages +│ ├── index.vue +│ └── user.vue +├── bknd.config.ts +├── bun.lock +├── nuxt.config.ts +├── package.json +├── public +│ ├── admin # generated by bknd and contains the admin UI +│ ├── bknd.ico +│ ├── bknd.svg +│ ├── favicon.ico +│ ├── file.svg +│ ├── globe.svg +│ ├── nuxt.svg +│ ├── robots.txt +│ └── window.svg +├── README.md +├── server +│ ├── middleware +│ │ └── bknd.ts # intercepts api and admin ui requests +│ ├── routes +│ │ └── todos.post.ts +│ └── utils +│ └── bknd.ts # initializes bknd instance +└── tsconfig.json +``` + +Here is a quick overview about how to adjust the behavior of `bknd`: +* Initialization of the `bknd` config with helper functions are located at `src/server/utils/bknd.ts` +* Admin UI is rendered at `src/server/middleware/bknd.ts` + +## Commands + +All commands are run from the root of the project, from a terminal: + +| Command | Action | +|:--------------------------|:-------------------------------------------------| +| `npm install` | Installs dependencies | +| `npm run dev` | Starts local dev server at `localhost:3000` | +| `npm run build` | Build your production site | + +## Want to learn more? + +Feel free to check [our documentation](https://docs.bknd.io/integration/nuxt) or jump into our [Discord server](https://discord.gg/952SFk8Tb8). \ No newline at end of file diff --git a/examples/nuxt/app/assets/css/main.css b/examples/nuxt/app/assets/css/main.css new file mode 100644 index 0000000..a88e69a --- /dev/null +++ b/examples/nuxt/app/assets/css/main.css @@ -0,0 +1,24 @@ +@import "tailwindcss"; + +:root { + --background: #ffffff; + --foreground: #171717; + font-weight: 400; +} + +@media (prefers-color-scheme: dark) { + :root { + --background: #0a0a0a; + --foreground: #ededed; + } +} + +@theme { + --color-background: var(--background); + --color-foreground: var(--foreground); +} + +body { + @apply bg-background text-foreground; + font-family: Arial, Helvetica, sans-serif; +} diff --git a/examples/nuxt/app/components/Buttons.vue b/examples/nuxt/app/components/Buttons.vue new file mode 100644 index 0000000..864dc3a --- /dev/null +++ b/examples/nuxt/app/components/Buttons.vue @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/examples/nuxt/app/components/Footer.vue b/examples/nuxt/app/components/Footer.vue new file mode 100644 index 0000000..be698bf --- /dev/null +++ b/examples/nuxt/app/components/Footer.vue @@ -0,0 +1,29 @@ + + + diff --git a/examples/nuxt/app/components/List.vue b/examples/nuxt/app/components/List.vue new file mode 100644 index 0000000..a3fddc2 --- /dev/null +++ b/examples/nuxt/app/components/List.vue @@ -0,0 +1,21 @@ + + + \ No newline at end of file diff --git a/examples/nuxt/app/composables/useTodoActions.ts b/examples/nuxt/app/composables/useTodoActions.ts new file mode 100644 index 0000000..138a9d2 --- /dev/null +++ b/examples/nuxt/app/composables/useTodoActions.ts @@ -0,0 +1,31 @@ +import type { DB } from "bknd"; + +type Todo = DB["todos"]; + +export const useTodoActions = () => { + const fetchTodos = () => + $fetch<{ limit: number; todos: Array; 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 }; +}; diff --git a/examples/nuxt/app/composables/useUser.ts b/examples/nuxt/app/composables/useUser.ts new file mode 100644 index 0000000..1dc371a --- /dev/null +++ b/examples/nuxt/app/composables/useUser.ts @@ -0,0 +1,6 @@ +import type { User } from "bknd"; + +export const useUser = () => { + const getUser = () => $fetch("/api/auth/me") as Promise<{ user: User }>; + return { getUser }; +}; diff --git a/examples/nuxt/app/pages/index.vue b/examples/nuxt/app/pages/index.vue new file mode 100644 index 0000000..9795a16 --- /dev/null +++ b/examples/nuxt/app/pages/index.vue @@ -0,0 +1,64 @@ + + + diff --git a/examples/nuxt/app/pages/user.vue b/examples/nuxt/app/pages/user.vue new file mode 100644 index 0000000..5ac2e27 --- /dev/null +++ b/examples/nuxt/app/pages/user.vue @@ -0,0 +1,49 @@ + + + \ No newline at end of file diff --git a/examples/nuxt/bknd.config.ts b/examples/nuxt/bknd.config.ts new file mode 100644 index 0000000..7a0ba13 --- /dev/null +++ b/examples/nuxt/bknd.config.ts @@ -0,0 +1,58 @@ +import { em, entity, text, boolean, } from "bknd"; +import { secureRandomString } from "bknd/utils"; +import type { NuxtBkndConfig } from "bknd/adapter/nuxt"; +import { registerLocalMediaAdapter } from "bknd/adapter/node"; + +const local = registerLocalMediaAdapter(); + +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), + }, + }, + media: { + enabled: true, + adapter: local({ + path: "./public/uploads", + }), + }, + }, + adminOptions: { + adminBasepath: "/admin", + assetsPath: "/admin/", + }, +} satisfies NuxtBkndConfig; diff --git a/examples/nuxt/nuxt.config.ts b/examples/nuxt/nuxt.config.ts new file mode 100644 index 0000000..a179d24 --- /dev/null +++ b/examples/nuxt/nuxt.config.ts @@ -0,0 +1,11 @@ +// https://nuxt.com/docs/api/configuration/nuxt-config +export default defineNuxtConfig({ + compatibilityDate: "2025-07-15", + devtools: { enabled: false }, + modules: ["@nuxtjs/tailwindcss"], + app: { + head: { + title: "Nuxt 🤝 Bknd.io", + }, + }, +}); diff --git a/examples/nuxt/package.json b/examples/nuxt/package.json new file mode 100644 index 0000000..139736d --- /dev/null +++ b/examples/nuxt/package.json @@ -0,0 +1,21 @@ +{ + "name": "nuxbknd", + "type": "module", + "private": true, + "scripts": { + "build": "nuxt build", + "dev": "nuxt dev", + "generate": "nuxt generate", + "preview": "nuxt preview", + "typegen": "bunx tsx node_modules/.bin/bknd types --outfile bknd-types.d.ts", + "postinstall": "nuxt prepare && bun run bknd copy-assets --out public/admin" + }, + "dependencies": { + "@nuxtjs/tailwindcss": "6.14.0", + "@types/node": "^25.2.3", + "bknd": "file:../../app", + "nuxt": "^4.3.1", + "vue": "^3.5.28", + "vue-router": "^4.6.4" + } +} diff --git a/examples/nuxt/public/bknd.ico b/examples/nuxt/public/bknd.ico new file mode 100644 index 0000000000000000000000000000000000000000..c1a946d533a71c15e51fc4cade87289ca467b57d GIT binary patch literal 15086 zcmeI3`*+mE6~H&yJT}=RyV=ci12qaFP%EgQVoRW{Xhk^@c}Z;%A4d^I@qsT68WgDr zC?W!C#R3W_pz@Gs1lrn`3M%~v`jh`ed)nUK&m@yhzWJ{E-E1D4bDDF8`F_7UGxs@n z?%aC`g$hF9(D?Bo?VCe;E)9jQ357z<&7JS@Q0VXaEv`MkFDeR!R_lP#I!3327HZ?W z`5W2Mx&OAG8y-G+zG;@W^VxYWGj{mhuUjJ>a%)h5nZk-jC2Op2g z&VvnxmVBoH+!ranvFbumduv)jK}bf8DV8^XS8HUFuQZ@9>))x7xP!Z;f&SW`MC8El zQ~fRn@Hq8thu&txE9U)Db;iZneXgi1l#TDJ40P>m)(twAy>|3$O5WL1C$GF!WBOyN z4}1@@2IJ6o7iagmxVS*pY_2i939?=`V}O0|4=I^XMJbJ@jQt#!oEm0Tl8=u!_Sg&TkDuODCdkUl&GvNst}2z6UQf!g zvmJOmwX)jaXYozfqo1QMFEMgt%@gN3&YR{D#XV?d4fX_M{BTY84O-D(KRd?vy(b#w zu0>UH(Iv%lo9f8JPsP*P-E?$Za%r)={6^C3QE*4kCjL0mwFl5yZ1#tT`~K{-vP56d zwc?WQ`z{^M?sw!+Syd<#Cq;~17$=`*9oCi%hW&u{R4^9Oa^ ze5Gvgwqx1LpPW)XQGWClzCqSP#%t6*aV}e2dehf8hGo|g-J_lMG#47+DK-*6jdO^1 z?|RUN4LYMbyGrdx*mGuPOG86bk?aQl9Qn`uje2%XyQSRd^dvoN+twwGZbAqAh8A=l zKlSd#Rptyx=Whh1V@x0D5BP1}RVP2tv(<%*eOspI{`Sx6W%+Ls#=qm79C2B(O#XS9 z-1AV!8Q{*z_IbM6eS8Y9oU?uBT=#>`ePG0>V!3>LiOiT2mF24wvg1I#9Q{km$b@su zZ(fjZrI7+zsWL_%v&GjPkIr8=IpT{)mHIL-tW6jj!5s9+?hE?upVrI8BmK|*Of*cL zR+g1-1A_zTmsX-S({M+W#Q@VGj46u|F!2`yY+TA=NeR5ZtF` zs!rbA8kISUGxwKm`|6D^9AsUKQ*ZiA`r@t$y{+@2g1oplfEzSnQ|MWX-re`^{K0$% zFKiR`hqI6O!NeyMYt=@ckx^Ml*KBZN0 zKcZ*tqtC_7eX*ppKq6%Y(loqCQrd_|VDopUbWvz*#yFaoyg~0{;8d-yf;|vWv6(%z1nG zPr>42y}=>ZIruhr-s>M@;O@ma3!S;b>96_Vw&!SrO#WF}x4UAg+M!!+k4j$K&dxmq z4a5kD5w^~a3chyR+NAMy@|pLpOu-YMrpv!kyE;^Gcj?HJ=b@YfCihMbK5uPC2HdE9 zpax<{)^8}@v)jK(Q9nZs$^~shp%wytr`BiI$*)u4GkSn(c~Pj1zJP!J|JR=!Xk&5Z z^BH>OJ9S#k+NNqNuytBm%jNx3d9`RetL@*~25P?p>nm-*rXMJ{UO3SGuR(uaht{WGtTuN~)l=h-6Z_xtVQw)*d>9)~ z_&$FIp10gk*5e-Vz|xqR-y1yv9x(y(ue4pCr_W;`Z(@U%cH7_gNX*nUcyWhcR#$9} z8anMbdU6@$PwiS0G#9#$oTvWgrH8f1do-lNW;}ThVz^UpEHk-M?m&z&bpkq{y!pKk zR~eoW8=o;dD$l=~F!5@}fy-;})=ETU@D@Jtcyx;2yzuS&7R}iaCw%0YxZxeSjXM@r znmjf+5aMyfaxI>AZF=&=Q#5A2^FV`H|M3;oQsU%}sj+x__xJ0Q#oh9fcVJQ%@Qc=R zA)iU!#f9sArlw-fuPbEc9Tlb)0zMLhTdFaZ-sV!MYuT!M0UEsU{NBO!bCnsgM&{(Y zEsuNC!w+JKYs!dEEy+gOXHAv~; zql3iSgTRO9OB9~F7Wp1@Xz>#z?khL!H1>!%sW&&w zT+R^kmqBT_`~U~+^*TrY>~FSb47EMzA^BeR13Ez+4)&KE5p{Oi!gkkBxBnOe?c~Lm zJRR@Kf3P?F&L6vGu065`C+Z2PPo#!%pyG+tTT&xN9v$0#qxwYTA$!Z;YiX*VO0|p)6*W=tf z@Ic?cp}VDR5Ti4|CQd%ynbgx^_{dl5N$@~@VE>GDmiVd>`?n@D~1w*B>{783dK zo#w*!J~NNIEj}tacQZb~Jl0_>b@jFm5c#?C^}_OdNADKg-%;C0eQCh?-Ru*(ZT` + + + \ No newline at end of file diff --git a/examples/nuxt/public/favicon.ico b/examples/nuxt/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..18993ad91cfd43e03b074dd0b5cc3f37ab38e49c GIT binary patch literal 4286 zcmeHLOKuuL5PjK%MHWVi6lD zOGiREbCw`xmFozJ^aNatJY>w+g ze6a2@u~m#^BZm@8wco9#Crlli0uLb^3E$t2-WIc^#(?t)*@`UpuofJ(Uyh@F>b3Ph z$D^m8Xq~pTkGJ4Q`Q2)te3mgkWYZ^Ijq|hkiP^9`De={bQQ%heZC$QU2UpP(-tbl8 zPWD2abEew;oat@w`uP3J^YpsgT%~jT(Dk%oU}sa$7|n6hBjDj`+I;RX(>)%lm_7N{+B7Mu%H?422lE%MBJH!!YTN2oT7xr>>N-8OF$C&qU^ z>vLsa{$0X%q1fjOe3P1mCv#lN{xQ4_*HCSAZjTb1`}mlc+9rl8$B3OP%VT@mch_~G z7Y+4b{r>9e=M+7vSI;BgB?ryZDY4m>&wcHSn81VH1N~`0gvwH{ z8dv#hG|OK`>1;j7tM#B)Z7zDN?{6=dUal}$e \ No newline at end of file diff --git a/examples/nuxt/public/globe.svg b/examples/nuxt/public/globe.svg new file mode 100644 index 0000000..567f17b --- /dev/null +++ b/examples/nuxt/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/nuxt/public/nuxt.svg b/examples/nuxt/public/nuxt.svg new file mode 100644 index 0000000..5ad1e9d --- /dev/null +++ b/examples/nuxt/public/nuxt.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/nuxt/public/robots.txt b/examples/nuxt/public/robots.txt new file mode 100644 index 0000000..0ad279c --- /dev/null +++ b/examples/nuxt/public/robots.txt @@ -0,0 +1,2 @@ +User-Agent: * +Disallow: diff --git a/examples/nuxt/public/window.svg b/examples/nuxt/public/window.svg new file mode 100644 index 0000000..b2b2a44 --- /dev/null +++ b/examples/nuxt/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/nuxt/server/middleware/bknd.ts b/examples/nuxt/server/middleware/bknd.ts new file mode 100644 index 0000000..d298768 --- /dev/null +++ b/examples/nuxt/server/middleware/bknd.ts @@ -0,0 +1,15 @@ +import { serve } from "bknd/adapter/nuxt"; +import config from "../../bknd.config"; + +export default defineEventHandler(async (event) => { + const pathname = event.path + const request = toWebRequest(event); + + if (pathname.startsWith("/api") || pathname !== "/") { + const res = await serve(config, process.env)(request); + + if (res && res.status !== 404) { + return res; + } + } +}); diff --git a/examples/nuxt/server/routes/todos.post.ts b/examples/nuxt/server/routes/todos.post.ts new file mode 100644 index 0000000..314e0ff --- /dev/null +++ b/examples/nuxt/server/routes/todos.post.ts @@ -0,0 +1,31 @@ +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 }; + } + } + +}); diff --git a/examples/nuxt/server/utils/bknd.ts b/examples/nuxt/server/utils/bknd.ts new file mode 100644 index 0000000..6b8d02a --- /dev/null +++ b/examples/nuxt/server/utils/bknd.ts @@ -0,0 +1,21 @@ +import { type NuxtBkndConfig, getApp as getNuxtApp } from "bknd/adapter/nuxt"; +import bkndConfig from "../../bknd.config"; + +export async function getApp( + config: NuxtBkndConfig, + 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(); +} diff --git a/examples/nuxt/tsconfig.json b/examples/nuxt/tsconfig.json new file mode 100644 index 0000000..307b213 --- /dev/null +++ b/examples/nuxt/tsconfig.json @@ -0,0 +1,18 @@ +{ + // https://nuxt.com/docs/guide/concepts/typescript + "files": [], + "references": [ + { + "path": "./.nuxt/tsconfig.app.json" + }, + { + "path": "./.nuxt/tsconfig.server.json" + }, + { + "path": "./.nuxt/tsconfig.shared.json" + }, + { + "path": "./.nuxt/tsconfig.node.json" + } + ] +}