Service Bindings

Cloudflare Workers provide additional runtime APIs on their platform, such as Durable Objects & KV. You can access these bindings via the env property during a fetch event.

Table of contents:

  • KV - low-latency, key-value data store.
  • Durable Objects - low-latency coordination and consistent storage.

KV#

Workers KV is a global, low-latency, key-value data store. It stores data in a small number of centralized data centers, then caches that data in Cloudflare's data centers after access. KV supports exceptionally high read volumes with low latency, making it possible to build highly dynamic APIs and websites that respond as quickly as a cached static file would. While reads are periodically revalidated in the background, requests which are not in cache and need to hit the centralized back end can see high latencies. Learn more.

To use KV, you must first create a KV namespace on your account. You can use the wranger CLI to create a new namespace.

Once created, ensure it is referenced in your wrangler.toml file, for example:

kv_namespaces = [
  { binding = "TODOS", id = "06779da6940b431db6e566b4846d64db" }
]

Next, call the getKVNamespace method on the Environment class, referencing the KV namespace binding name (in this case "TODOS"). An error will be thrown if the namespace does not exist.

import 'package:cloudflare_workers/cloudflare_workers.dart';

void main() {
  CloudflareWorkers(
    fetch: (request, env, ctx) async {
      final todos = env.getKVNamespace('TODOS');
      // ...
    },
  );
}

You can then interact with your KV namespace. For example, to put a new value into KV:

await todos.put('write-docs', 'in-progress');

You can access values too:

final status = await todos.get('write-docs');

KV supports additional features, such as reading and writing metadata, JSON values, listing keys, writing buffers and much more.

Durable Objects#

Durable Objects provide low-latency coordination and consistent storage for the Workers platform. A given namespace can support essentially unlimited Durable Objects, with each Object having access to a transactionally consistent key-value storage API. Learn more.

To use Durable Objects, first define the Durable Object name and class_name in your wrangler.toml file:

durable_objects.bindings = [
  {name = "VIEWS", class_name = "ViewsDurableObject"},
]

Next, create a new Dart class which extends the DurableObject class. You must implement the fetch class by default, however alarms are also supported.

import 'package:cloudflare_workers/cloudflare_workers.dart';

class ViewsDurableObject extends DurableObject {
  @override
  Future<Response> fetch(Request request, DurableObjectEnv env) async {
    // ...
  }
}

Define the class instance in the CloudflareWorkers instance, provinding a Map whose key is the class_name and a function returning a new instance of your class.

import 'package:cloudflare_workers/cloudflare_workers.dart';

class ViewsDurableObject extends DurableObject { ... }

void main() {
  CloudflareWorkers(
    durableObjects: {
      'ViewsDurableObject': () => ViewsDurableObject(),
    },
    fetch: (request, env, ctx) async {
      // ...
    },
  );
}

You can then access the Durable Object namespace via the Environment during a fetch request, referencing the name of the namespace. If the namespace does not exist, an error will be thrown.

void main() {
  CloudflareWorkers(
    durableObjects: {
      'ViewsDurableObject': () => ViewsDurableObject(),
    },
    fetch: (request, env, ctx) async {
      final views = namespace.getDurableObjectNamespace('VIEWS');
      final id = views.idFromName('...');
      return views.get(id).fetch(request);
    },
  );
}

State, Storage & Alarms#

Each Durable Object instance has access to it's own state (and the environment). You can access Durable Object State via the state getter. The state instance provides access to the Durable Object's unique ID, as well as a transaction storage API.

To access the storage API, call the storage property on the state instance. Here you can manage persisted storage for this specific Durable Object:

class ViewsDurableObject extends DurableObject {
  @override
  Future<Response> fetch(Request request, DurableObjectEnv env) async {
    final views = (await state.storage.get<int>('views') ?? 0) + 1;
    await state.storage.put('views', views);
    return Response.ok('This Durable Object has been requested $views times!');
  }
}

Additionally you can manage alarms, enabling you to schedule events to occur at a specific time:

class ViewsDurableObject extends DurableObject {
  @override
  Future<Response> fetch(Request request, DurableObjectEnv env) async {
    // Add some data to the DO storage.
    await state.storage.put(
      DateTime.now().toIso8601String(),
      {
        'job': '1234',
        'data': {
          'foo': 'bar',
        }
      }
    );

    // If not already set, set an alarm for 60 seconds from now.
    if (await state.storage.getAlarm() == null) {
      await state.storage.setAlarm(DateTime.now().add(Duration(seconds: 60)));
    }

    return Response.ok('Job queued!');
  }

  @override
  FutureOr<void> alarm() {
    print('Alarm triggered, processing jobs...');
    final jobs = await state.storage.list();
    // Process jobs...
  }
}