bknd logo
Frameworks

Solid Start

Run bknd inside Solid Start

Installation

To get started with Solid Start and bknd you can either install the package manually, and follow the descriptions below, or use the CLI starter.

Create a new Solid Start 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 { SolidStartBkndConfig } from "bknd/adapter/solid-start";
import { em, entity, text, boolean } from "bknd";
import { secureRandomString } from "bknd/utils";

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),
      },
    },
  },
  // please run `bun bknd copy-assets --out public/admin` to copy the admin assets into the public directory
  // alternatively add this as a postinstall script in your package.json like this:
  // "postinstall": "bknd copy-assets --out public/admin"
  adminOptions: {
    adminBasepath: "/admin",
    assetsPath: "/admin/", // trailing slash is important
  },
} satisfies SolidStartBkndConfig;

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

export type SolidStartBkndConfig<Env = SolidStartEnv> = RuntimeBkndConfig<Env>;

Serve the API and Admin UI

The Solid Start adapter uses middleware to handle API requests and serve the Admin UI. Create a src/middleware/index.ts file:

src/middleware/index.ts
import { createMiddleware } from "@solidjs/start/middleware";
import config from "../../bknd.config";
import { serve } from "bknd/adapter/solid-start";

const handler = serve(config);

export default createMiddleware({
  onRequest: async (event) => {
    const url = new URL(event.request.url);
    const pathname = url.pathname;

    if (pathname.startsWith("/api") || pathname !== "/") {
      const res = await handler(event.request);

      if (res && res.status !== 404) {
        return res;
      }
    }
  },
});

Update the app config (Solid Start v1 based on vinxi)

Add the following to your app.config.ts file:

app.config.ts
import { defineConfig } from "@solidjs/start/config";

const app = defineConfig({
  // ... your existing config
  middleware: "src/middleware/index.ts",
  // ... your existing config
});

export default app;

Update the vite config (Solid Start v2)

Add the following to your vite.config.ts file:

vite.config.ts
import { defineConfig } from "vite";
import solid from "vite-plugin-solid";
import { solidStart } from "@solidjs/start/vite";

export default defineConfig({
  plugins: [
    solid(),
    solidStart({
      middleware: "./src/middleware/index.ts",
    }),
  ],
});

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:

src/lib/bknd.ts
import { getApp as getBkndApp } from "bknd/adapter/solid-start";
import bkndConfig from "../../bknd.config";
import type { App } from "bknd";

export const getApp = async () => {
  return await getBkndApp(bkndConfig);
};

export async function getApi({
  headers,
  verify,
}: {
  verify?: boolean;
  headers?: Headers;
}) {
  const app = await getApp();

  if (verify) {
    const api = app.getApi({ headers });
    await api.verifyAuth();
    return api;
  }

  return app.getApi();
};

Example usage of the API

You can use the getApi helper function we've already set up to fetch and mutate in static pages and server components:

src/routes/index.tsx
import { getApi } from "~/lib/bknd";
import { query, createAsync } from "@solidjs/router";

export const getTodo = async () => {
  "use server"
  const api = await getApi({});
  const limit = 5;
  const todos = await api.data.readMany("todos");
  const total = todos.body.meta.total as number;
  return { total, todos: todos as unknown as Todo[], limit };
};

const getTodosFromServer = query(async () => await getTodo(), "getTodosFromServer");

export default function Home() {
  const data = createAsync(() => getTodosFromServer());

  return (
    <ul>
     <For each={data()}>
      {(todo) => <li>{todo.title}</li>}
     </For>
    </ul>
  );
}

Using authentication

When using authentication, you'll need to pass the headers to the getApi function to verify the user is authenticated.

src/routes/auth.tsx
import { createResource, Suspense } from "solid-js";
import { A } from "@solidjs/router";
import { getRequestEvent } from "solid-js/web";
import { getApi } from "~/lib/bknd";

export const getUser = async () => {
  "use server"
  const request = getRequestEvent()?.request;
  const api = await getApi({ verify: true, headers: request?.headers });
  return api.getUser();
}

export default function Auth() {
  const [data] = createResource(async () => {
    const user = await getUser()
    return { user };
  }, {
    initialValue: {
      user: null
    }
  });

  return (
    <div class="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
      <main class="flex flex-col gap-8 row-start-2 items-center sm:items-start">
        <div class="flex flex-row items-center ">
          <img
            class="dark:invert size-18"
            src="/solid.svg"
            alt="Solid logo"
          />
          <div class="ml-3.5 mr-2 opacity-70">&amp;</div>
          <img
            class="dark:invert"
            src="/bknd.svg"
            alt="bknd logo"
            width={183}
            height={59}
          />
        </div>

        <Suspense fallback={<p>Loading...</p>}>
          <div>
            {data()?.user ? (
              <>
                Logged in as {data()?.user?.email}.
                <A
                  class="underline"
                  target="_self"
                  href={"/api/auth/logout"}
                >
                  Logout
                </A>
              </>
            ) : (
              <div class="flex flex-col gap-1">
                <p>
                  Not logged in.
                  <A
                    class="underline"
                    target="_self"
                    href={"/admin/auth/login"}
                  >
                    Login
                  </A>
                </p>
                <p class="text-xs opacity-50">
                  Sign in with:
                  <b>
                    <code>test@bknd.io</code>
                  </b>
                  /
                  <b>
                    <code>12345678</code>
                  </b>
                </p>
              </div>
            )}
          </div>
        </Suspense>
      </main>
    </div>
  );
}

Tips

Use target="_self" on links, anytime your traversing to an external route (like /api/auth/logout which are handled by bknd's middleware) to prevent solid router from intercepting the link.

<A href="/api/auth/logout" target="_self">
  Logout
</A>