---
title: "Tanstack Start"
description: "Run bknd inside Tanstack Start"
tags: ["documentation"]
---

## Installation

To get started with Tanstack Start and bknd, create a new Tanstack Start project by following the [official guide](https://tanstack.com/start/latest/docs/framework/react/getting-started#start-a-new-project-from-scratch), 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 TanstackStartConfig } from "bknd/adapter/tanstack-start";
import { em, entity, text, boolean } from "bknd";

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: "random_gibberish_please_change_this",
        // use something like `openssl rand -hex 32` for production
      },
    },
  },
  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 TanstackStartConfig;
```

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 `TanstackStartConfig` type extends the base config type with the following properties:

```typescript
export type TanstackStartConfig<Env = TanstackStartEnv> = FrameworkBkndConfig<Env>;
```

## Serve the API

The Tanstack Start adapter uses Tanstack Start's hooks mechanism to handle API requests. Create a `/src/routes/api.$.ts` file:

```typescript title="/src/routes/api.$.ts"
import { createFileRoute } from "@tanstack/react-router";
import config from "../../bknd.config";
import { serve } from "bknd/adapter/tanstack-start";

const handler = serve(config);

export const Route = createFileRoute("/api/$")({
  server: {
    handlers: {
      ANY: async ({ request }) => await handler(request),
    },
  },
});
```

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/bknd.ts"
import config from "../bknd.config";
import { getApp } from "bknd/adapter/tanstack-start";

export async function getApi({
  headers,
  verify,
}: {
  verify?: boolean;
  headers?: Headers;
}) {
  const app = await getApp(config, 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 Tanstack Start uses Nitro underneath and it will use polyfills for `process.env` making it platform/runtime agnostic.
</Callout>

## Enabling the Admin UI

Create a page at /src/routes/admin.$.tsx:

```typescript title="/src/routes/admin.$.tsx"
import { createFileRoute } from "@tanstack/react-router";
import { useAuth } from "bknd/client";
import "bknd/dist/styles.css";
import { Admin } from "bknd/ui";

export const Route = createFileRoute("/admin/$")({
  ssr: false, // [!code highlight] "data-only" works too
  component: RouteComponent,
});

function RouteComponent() {
  const { user } = useAuth();
  return (
    <Admin
      withProvider={{ user: user }}
      config={{
        basepath: "/admin",
        logo_return_path: "/../",
        theme: "system",
      }}
      baseUrl={import.meta.env.APP_URL}
    />
  );
};
```

<Callout type="info">
  Admin routes are expected to run on the client not using `ssr: false` will cause errors like `✘ [ERROR] No matching export in "node_modules/json-schema-library/dist/index.mjs" for import "Draft2019"` and production build might fail because of this
</Callout>


## Example usage of the API

You can use the `getApp` function to access the bknd API in your app:
These are a few examples how you can validate user and handle server-side requests using `createServerFn`.

```typescript title="src/routes/index.tsx"
import { createFileRoute } from "@tanstack/react-router";
import { getApi } from "@/bknd";
import { createServerFn } from "@tanstack/react-start";

export const getTodo = createServerFn()
  .handler(async () => {
    const api = await getApi({});
    const limit = 5;
    const todos = await api.data.readMany("todos", { limit, sort: "-id" });
    return { todos };
  });

export const Route = createFileRoute("/")({
  ssr: false,
  component: App,
  loader: async () => {
    return await getTodo();
  },
});

function App() {
  const { todos } = Route.useLoaderData();

  return (
    <div>
      <h1>Todos</h1>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    </div>
  );
}
```



### Using authentication

To use authentication in your app, pass the request headers to the API:

```typescript title="src/routes/user.tsx"
import { getApi } from "@/bknd";
import { createServerFn } from "@tanstack/react-start";
import { Link } from "@tanstack/react-router";
import { createFileRoute } from "@tanstack/react-router";
import { getRequest } from "@tanstack/react-start/server";

export const getUser = createServerFn()
  .handler(async () => {
    const request = getRequest();
    const api = await getApi({ verify: true, headers: request.headers });
    const user = api.getUser();
    return { user };
  });

export const Route = createFileRoute("/user")({
  component: RouteComponent,
  loader: async () => {
    return { user: await getUser() };
  },
});

function RouteComponent() {
  const { user } = Route.useLoaderData();
  return (
    <div>
      {user ? (
        <>
          Logged in as {user.email}.{" "}
          <Link
            className="font-medium underline"
            to={"/api/auth/logout" as string}
          >
            Logout
          </Link>
        </>
      ) : (
        <div className="flex flex-col gap-1">
          <p>
            Not logged in.
            <Link
              className="font-medium underline"
              to={"/admin/auth/login" as string}
            >
              Login
            </Link>
          </p>
          <p className="text-xs opacity-50">
            Sign in with:
            <b>
              <code>test@bknd.io</code>
            </b>
            /
            <b>
              <code>12345678</code>
            </b>
          </p>
        </div>
      )}
    </div>
  )
}
```

Check the [Tanstack Start repository example](https://github.com/bknd-io/bknd/tree/main/examples/tanstack-start) for more implementation details.
