bknd logo
Frameworks

React Router

Run bknd inside React Router

Installation

To get started with React Router and bknd you can either install the package manually, and follow the descriptions below, or use the CLI starter:

CLI Starter

Create a new React Router CLI starter project by running the following command:

  npx bknd create -i react-router

Manual

Create a new React Router project by following the official guide, and then install bknd as a dependency:

npm install bknd
pnpm install bknd
yarn add bknd
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 if necessary.

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.

bknd.config.ts
import type { ReactRouterBkndConfig } from "bknd/adapter/react-router";

export default {
  connection: {
    url: "file:data.db",
  },
} satisfies ReactRouterBkndConfig;

See bknd.config.ts for more information on how to configure bknd. The ReactRouterBkndConfig type extends the BkndConfig type with the following additional properties:

type ReactRouterEnv = NodeJS.ProcessEnv;
type ReactRouterFunctionArgs = {
  request: Request;
};
export type ReactRouterBkndConfig<Env = ReactRouterEnv> =
  FrameworkBkndConfig<Env>;

Serve the API

Create a helper file to instantiate the bknd instance and retrieve the API, importing the configurationfrom the bknd.config.ts file:

app/bknd.ts
import {
  type ReactRouterBkndConfig,
  getApp as getBkndApp,
} from "bknd/adapter/react-router";
import config from "../bknd.config";

export { config };

// you may adjust this function depending on your runtime environment.
// e.g. when deploying to cloudflare workers, you'd want the FunctionArgs to be passed in
// to resolve environment variables
export async function getApp() {
  return await getBkndApp(config, process.env as any);
}

export async function getApi(
  args?: { request: Request },
  opts?: { verify?: boolean },
) {
  const app = await getApp();
  if (opts?.verify) {
    const api = app.getApi({ headers: args?.request.headers });
    await api.verifyAuth();
    return api;
  }

  return app.getApi();
}

For more information about the connection object, refer to the Database guide.

Create a new api splat route file at app/routes/api.$.ts:

app/routes/api.$.ts
import { getApp } from "~/bknd";

const handler = async (args: { request: Request }) => {
  const app = await getApp();
  return app.fetch(args.request);
};

export const loader = handler;
export const action = handler;

Enabling the Admin UI

Create a new splat route file at app/routes/admin.$.tsx:

app/routes/admin.$.tsx
import { lazy, Suspense, useSyncExternalStore } from "react";
import { type LoaderFunctionArgs, useLoaderData } from "react-router";
import { getApi } from "~/bknd";

const Admin = lazy(() =>
  import("bknd/ui").then((mod) => ({ default: mod.Admin })),
);
import "bknd/dist/styles.css";

export const loader = async (args: LoaderFunctionArgs) => {
  const api = await getApi(args, { verify: true });
  return {
    user: api.getUser(),
  };
};

export default function AdminPage() {
  const { user } = useLoaderData<typeof loader>();
  // derived from https://github.com/sergiodxa/remix-utils
  // @ts-ignore
  const hydrated = useSyncExternalStore(
    () => {},
    () => true,
    () => false,
  );
  if (!hydrated) return null;

  return (
    <Suspense>
      <Admin
        withProvider={{ user }}
        config={{ basepath: "/admin", logo_return_path: "/../" }}
      />
    </Suspense>
  );
}

Example usage of the API

You can use the getApi helper function we've already set up to fetch and mutate:

app/routes/_index.tsx
import { useLoaderData, type LoaderFunctionArgs } from "react-router";
import { getApi } from "~/bknd";

export const loader = async (args: LoaderFunctionArgs) => {
  // use authentication from request
  const api = await getApi(args, { verify: true });
  const { data } = await api.data.readMany("todos");
  return { data, user: api.getUser() };
};

export default function Index() {
  const { data, user } = useLoaderData<typeof loader>();

  return (
    <div>
      <h1>Data</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
      <h1>User</h1>
      <pre>{JSON.stringify(user, null, 2)}</pre>
    </div>
  );
}