Installation

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

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

npx bknd create -i cloudflare

Serve the API

If you don’t choose anything specific, the following code will use the warm mode and uses the first D1 binding it finds. See the chapter Using a different mode for available modes.

src/index.ts
import { serve, d1 } from "bknd/adapter/cloudflare";

// scans your environment for the first D1 binding it finds
export default serve();

// manually specifying a D1 binding:
export default serve<Env>({
  app: ({ env }) => d1({ binding: env.D1_BINDING })
});

// or specify binding using `bindings`
export default serve<Env>({
  bindings: ({ env }) => ({ db: env.D1_BINDING })
});

// or use LibSQL
export default serve<Env>({
  app: ({ env }) => ({ url: env.DB_URL })
});

For more information about the connection object when using LibSQL, refer to the Database 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. You can do so by either serving the static using the new Assets feature, or the deprecated Workers Site.

Make sure your assets point to the static assets included in the bknd package:

wrangler.toml
assets = { directory = "node_modules/bknd/dist/static" }

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";

export default serve<Env>({
   // ...
   onBuilt: async (app) => {
      app.server.get("/hello", (c) => c.json({ hello: "world" }));
   }
});

The property app.server is a Hono instance, you can literally anything you can do with Hono.

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<Env>({
   // ...
   mode: "cache",
   bindings: ({ 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<Env>({
   // ...
   mode: "durable",
   bindings: ({ 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 }) => ({ 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"]