Add RavenDB to an Existing Cloudflare Worker (TypeScript)


Before We Get Started

You will need the following before continuing:

Installing the RavenDB Client SDK

Get started by installing the ravendb npm package in your project which provides the Node.js client SDK.

Using npm:

npm install ravendb

Support for Cloudflare Workers was added in 5.2.8+.

Initializing the Document Store

Import the DocumentStore from ravendb package to create a new instance with the required configuration and initialize your connection to RavenDB by calling the initialize method.
You can then export a function to initialize a document session to use in your Cloudflare Worker.

Example db.ts TypeScript module:

import { DocumentStore } from "ravendb";

const documentStore = new DocumentStore(
  ["https://a.free.mycompany.ravendb.cloud"],
  "demo",
  // authOptions
);

let initialized = false;

function initialize() {
    if (initialized) return;
    documentStore.initialize();
    initialized = true;
}

export function openAsyncSession() {
    if (!initialized) {
        initialize();
    }

    return documentStore.openSession();
}

For more on what options are available, see Creating a Document Store.

You can set options manually but it's more likely you'll want to set config variables in Wrangler or in Cloudflare to customize the document store initialization.

Updating Database Connection Settings

Enable Node Compatibility

Update your wrangler.toml file to enable Node.js compatibility by setting node_compat to true:

name = "ravendb-worker"
main = "./src/index.ts"
node_compat = true
compatibility_date = "2022-05-03"

This setting is required for the RavenDB Node.js SDK to operate correctly. If this is false or missing, you will experience runtime exceptions.

Define environment variables

You will also want to set environment variables to pass database information such as the URLs and database name:

# Define top-level environment variables
# under the `[vars]` block using
# the `key = "value"` format
[vars]
DB_URLS = "https://a.free.company.ravendb.cloud"
DB_NAME = "dev"

# Override values for `--env production` usage
[env.production.vars]
DB_URLS = "https://a.free.company.ravendb.cloud,https://b.free.company.ravendb.cloud"
DB_NAME = "prod"

There are two variables defined above:

  • DB_URLS -- These are the node URLs for your RavenDB instance (Cloud or self-hosted). The values are comma-separated.
  • DB_NAME -- This is the default database to connect to.

The defaults are under [vars] and overriden in [env.production.vars].

wrangler.toml overrides on deployment

You can also define settings within the Cloudflare worker dashboard. The values in the wrangler.toml will overwrite those values on new deployment. Keep this in mind when deciding where to define the variables!

Variables defined here will be exposed on the env variable passed to the root fetch function of the Worker.

For example, a barebones index.ts ES module could look like:

import { DocumentStore } from "ravendb";

let documentStore: DocumentStore;
let initialized = false;

function initialize({ urls, databaseName }) {
    if (initialized) return;
    documentStore = new DocumentStore(
      urls,
      databaseName
    );
    documentStore.initialize();
    initialized = true;
}

function openAsyncSession() {
    if (!initialized) {
        throw new Error("DocumentStore has not been initialized");
    }

    return documentStore.openSession();
}

export default {
    fetch(req, env, ctx) {
        const { DB_URLS, DB_NAME } = env;

        initialize({ 
            urls: DB_URLS.split(","), 
            databaseName: DB_NAME
        });

        ctx.db = openAsyncSession();

        // Handle request
        // ...
        return handleRequest(req, env, ctx);
    }
}

This creates a session-per-request and avoids re-initializing the document store.
The request handler can then pass the document session to middleware or route handlers as needed.
The gh-ravendb-template uses the itty-router package to make this easier.

Cloudflare Worker requests are isolated

Cloudflare Worker invocations do not incur cold start cost like other serverless platforms.
However, requests are isolated and modules are not shared between requests. This means that document store initialization is incurred every request, however the overhead is minimal.

For document caching during a session or across requests, using Cloudflare's KV natively is not yet supported by the RavenDB Node.js client SDK but could be implemented manually through application logic.

Configuring Support for Certificates

Client certificate authentication is handled through Cloudflare mTLS authentication for Workers.
You will need to upload your certificate to your Cloudflare account so that it can be accessed and bound to your Worker.

Obtain RavenDB certificate

First, download your RavenDB client certificate package you will use to authenticate.
Follow the guides for either Cloud certificates or for self-hosted certificates.
It is recommended to generate a new client certificate with limited access rights instead of a ClusterAdmin-level certificate.
This also ensures the Worker is using a dedicated certificate that can be managed separately.

Once extracted to a folder, you'll need the paths to the .crt and .key files for the next step.

Do not copy certificate files to the project

For Cloudflare Workers, you do not store your certificate files in your project directory.
Certificates are password-equivalents. Take care not to accidentally commit them to source control.
Keep them outside the project directory for this next step.

Upload certificate using wrangler

You will use Cloudflare's wrangler CLI to upload your .crt and .key files as an mTLS certificate.
You only need to do this once (and each time the certificate needs to be renewed).

wrangler global vs. local usage

This guide will use npx to execute wrangler to ensure the commands work across platforms.
You can also choose to install wrangler globally using npm i wrangler -g to use without npx, but you will need to keep it updated. Read more about Installing and updating Wrangler

In the project directory, run:

npx wrangler mtls-certificate upload --cert path/to/db.crt --key path/to/db.key --name ravendb_cert

This will display output like:

Uploading mTLS Certificate ravendb_cert...
Success! Uploaded mTLS Certificate ravendb_cert
ID: <CERTIFICATE_ID>
Issuer: CN=...
Expires on ...

Copy the <CERTIFICATE_ID> in the output for the next step.

Setup mTLS binding in wrangler

You will need to add a mTLS "binding" so that the certificate is made available and used by the Worker at runtime.

Edit your wrangler.toml file to update the following:

mtls_certificates = [
  { binding = "DB_CERT", certificate_id = "<CERTIFICATE_ID>" } 
]

Replace <CERTIFICATE_ID> with the Certificate ID you copied from the previous step.

Be sure to also update the DB_URLS and DB_NAME variables for your cluster.

For a deeper dive on what this is doing, you can read more about how mTLS bindings work in Cloudflare Workers.

Set custom fetcher for DocumentStore

Once the certificate binding is added, Cloudflare will create a DB_CERT object exposed through env.
You can then bind the provided env.DB_CERT.fetch custom fetch function to the DocumentStore using the DocumentConventions.customFetch option.

An updated example index.ts ES module:

import { DocumentStore } from "ravendb";

let documentStore: DocumentStore;
let initialized = false;

function initialize({ urls, databaseName, mtlsBinding }) {
    if (initialized) return;
    documentStore = new DocumentStore(
      urls,
      databaseName
    );

    // Bind custom mTLS binding `fetch()` function and ensure `this` remains bound to
    // original context
    documentStore.conventions.customFetch = mtlsBinding.fetch.bind(mtlsBinding);

    documentStore.initialize();
    initialized = true;
}

function openAsyncSession() {
    if (!initialized) {
        throw new Error("DocumentStore has not been initialized");
    }

    return documentStore.openSession();
}

export default {
    fetch(req, env, ctx) {
        const { DB_URLS, DB_NAME, DB_CERT } = env;

        initialize({ 
            urls: DB_URLS.split(","), 
            databaseName: DB_NAME,
            mtlsBinding: DB_CERT
        });

        ctx.db = openAsyncSession();

        // Handle request
        // ...
        return handleRequest(req, env, ctx);
    }
}

The DB_CERT variable is exposed on env and has a single fetch function that is automatically bound to the client certificate at runtime. Note that passing the function requires binding the this context to the original scope, otherwise you will run into closure-related exceptions.

The env.DB_CERT binding will not be available in local mode (--local), this is a known issue with Wrangler.

Add TypeScript Declaration for DB_CERT

If you are using TypeScript, env.DB_CERT will not be typed by default. You can create a globals.d.ts file in your project and add the following type declarations:

declare global {
  namespace NodeJS {
    interface ProcessEnv {
      DB_CERT?: { fetch: typeof fetch }
    }
  }
}

Configuring Cloudflare

There is no extra configuration necessary if you are providing all the connection information in your wrangler.toml file. However, you may want to override variables set in the [env.production.vars] through the Cloudflare Worker dashboard.

Navigate to your Worker Settings > Variables > Environment Variables and add variables to override, like DB_NAME and DB_URLS. You will also see the DB_CERT binding listed if the mTLS binding was successfully uploaded.

Cloudflare Worker environment variables and settings

Cloudflare Worker environment variables and settings

Next Steps