--- 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" ``` ## 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 Admin ``` 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. Check the [Nuxt repository example](https://github.com/bknd-io/bknd/tree/main/examples/nuxt) for more implementation details.