Installation

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

Serve the API

If you don’t choose anything specific, the following code will use the warm mode. See the chapter Using a different mode for available modes.

import { serve } from "bknd/adapter/cloudflare";

export default serve({
   app: (env: Env) => ({
      connection: {
         type: "libsql",
         config: {
            url: env.DB_URL,
            authToken: env.DB_TOKEN
         }
      }
   })
});

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

Now run the worker:

wrangler dev

And confirm it works by opening http://localhost:8787 in your browser.

Serve the Admin UI

Now in order to also server the static admin files, you have to modify the wrangler.toml to include the static assets:

[site]
bucket = "node_modules/bknd/dist/static"

And then modify the worker entry as follows:

import { serve } from "bknd/adapter/cloudflare";
import manifest from "__STATIC_CONTENT_MANIFEST";

export default serve({
   app: (env: Env) => ({
      connection: {
         type: "libsql",
         config: {
            url: env.DB_URL,
            authToken: env.DB_TOKEN
         }
      }
   }),
   manifest,
   setAdminHtml: true
});

Adding custom routes

You can also add custom routes by defining them after the app has been built, like so:

import { serve } from "bknd/adapter/cloudflare";
import manifest from "__STATIC_CONTENT_MANIFEST";

export default serve({
   app: (env: Env) => ({
      connection: {
         type: "libsql",
         config: {
            url: env.DB_URL,
            authToken: env.DB_TOKEN
         }
      }
   }),
   onBuilt: async (app) => {
      app.modules.server.get("/hello", (c) => c.json({ hello: "world" }));
   },
   manifest,
   setAdminHtml: true
});

Using a different mode

With the Cloudflare Workers adapter, you’re being offered to 4 modes to choose from (default: warm):

ModeDescriptionUse Case
freshOn every request, the configuration gets refetched, app built and then served.Ideal if you don’t want to deal with eviction, KV or Durable Objects.
warmIt tries to keep the built app in memory for as long as possible, and rebuilds if evicted.Better response times, should be the default choice.
cacheThe configuration is fetched from KV to reduce the initial roundtrip to the database.Generally faster response times with irregular access patterns.
durableThe bknd app is ran inside a Durable Object and can be configured to stay alive.Slowest boot time, but fastest responses. Can be kept alive for as long as you want, giving similar response times as server instances.

Modes: fresh and warm

To use either fresh or warm, all you have to do is adding the desired mode to cloudflare. mode, like so:

import { serve } from "bknd/adapter/cloudflare";

export default serve({
   /* ... */,
   mode: "fresh" // mode: "fresh" | "warm" | "cache" | "durable"
});

Mode: cache

For the cache mode to work, you also need to specify the KV to be used. For this, use the bindings property:

import { serve } from "bknd/adapter/cloudflare";

export default serve({
   /* ... */,
   mode: "cache",
   bindings: (env: Env) => ({ kv: env.KV })
});

Mode: durable (advanced)

To use the durable mode, you have to specify the Durable Object to extract from your environment, and additionally export the DurableBkndApp class:

import { serve, DurableBkndApp } from "bknd/adapter/cloudflare";

export { DurableBkndApp };
export default serve({
   /* ... */,
   mode: "durable",
   bindings: (env: Env) => ({ dobj: env.DOBJ }),
   keepAliveSeconds: 60 // optional
});

Next, you need to define the Durable Object in your wrangler.toml file (refer to the Durable Objects documentation):

[[durable_objects.bindings]]
name = "DOBJ"
class_name = "DurableBkndApp"

[[migrations]]
tag = "v1"
new_classes = ["DurableBkndApp"]

Since the communication between the Worker and Durable Object is serialized, the onBuilt property won’t work. To use it (e.g. to specify special routes), you need to extend from the DurableBkndApp:

import type { App } from "bknd";
import { serve, DurableBkndApp } from "bknd/adapter/cloudflare";

export default serve({
   /* ... */,
   mode: "durable",
   bindings: (env: Env) => ({ dobj: env.DOBJ }),
   keepAliveSeconds: 60 // optional
});

export class CustomDurableBkndApp extends DurableBkndApp {
   async onBuilt(app: App) {
      app.modules.server.get("/custom/endpoint", (c) => c.text("Custom"));
   }
}

In case you’ve already deployed your Worker, the deploy command may complain about a new class being used. To fix this issue, you need to add a “rename migration”:

[[durable_objects.bindings]]
name = "DOBJ"
class_name = "CustomDurableBkndApp"

[[migrations]]
tag = "v1"
new_classes = ["DurableBkndApp"]

[[migrations]]
tag = "v2"
renamed_classes = [{from = "DurableBkndApp", to = "CustomDurableBkndApp"}]
deleted_classes = ["DurableBkndApp"]