Key–value store
This API is available since Fedify 0.5.0.
The KvStore interface is a crucial component in Fedify, providing a flexible key–value storage solution for caching and maintaining internal data. This guide will help you choose the right KvStore implementation for your project and even create your own custom implementation if needed.
Choosing a KvStore implementation
Fedify offers several KvStore implementations to suit different needs.
Choose the implementation that best fits your project's requirements, considering factors like scalability, runtime environment, and distributed nature of your system.
MemoryKvStore
MemoryKvStore is a simple in-memory key–value store that doesn't persist data. It's best suited for development and testing environments where data don't have to be shared across multiple nodes. No setup is required, making it easy to get started.
- Best for
- Development and testing.
- Pros
- Simple, no setup required.
- Cons
- Data is not persistent, not suitable for production.
import { createFederation, MemoryKvStore } from "@fedify/fedify";
const federation = createFederation<void>({
kv: new MemoryKvStore(),
// ... other options
});SqliteKvStore
To use the SqliteKvStore, you need to install the @fedify/sqlite package first:
deno add jsr:@fedify/sqlitenpm add @fedify/sqlitepnpm add @fedify/sqliteyarn add @fedify/sqlitebun add @fedify/sqliteSqliteKvStore is a simple key–value store implementation that uses SQLite as the backend storage. It provides persistent storage with minimal configuration. It's suitable for development and testing. It uses native sqlite modules, node:sqlite for Node.js and Deno, bun:sqlite for Bun.
- Best for
- Development and testing.
- Pros
- Simple, persistent with minimal configuration.
- Cons
- Limited scalability, not suitable for high-traffic production.
import { DatabaseSync } from "node:sqlite";
import { createFederation } from "@fedify/fedify";
import { SqliteKvStore } from "@fedify/sqlite";
const db = new DatabaseSync(":memory:");
const federation = createFederation<void>({
// ...
kv: new SqliteKvStore(db),
});import { DatabaseSync } from "node:sqlite";
import { createFederation } from "@fedify/fedify";
import { SqliteKvStore } from "@fedify/sqlite";
const db = new DatabaseSync(":memory:");
const federation = createFederation<void>({
// ...
kv: new SqliteKvStore(db),
});import { Database } from "bun:sqlite";
import { createFederation } from "@fedify/fedify";
import { SqliteKvStore } from "@fedify/sqlite";
const db = new Database(":memory:");
const federation = createFederation<void>({
// ...
kv: new SqliteKvStore(db),
});DenoKvStore (Deno only)
WARNING
The @fedify/fedify/x/denokv module is deprecated and will be removed in Fedify 2.0.0. Please use the @fedify/denokv package instead.
To use the DenoKvStore, you need to install the @fedify/denokv package first:
deno add jsr:@fedify/denokvDenoKvStore is a key–value store implementation for Deno runtime that uses Deno's built-in Deno.openKv() API. It provides persistent storage and good performance for Deno environments. It's suitable for production use in Deno applications.
- Best for
- Production use in Deno environments.
- Pros
- Persistent storage, good performance, easy to set up.
- Cons
- Only available in Deno runtime.
import { createFederation } from "@fedify/fedify";
import { DenoKvStore } from "@fedify/denokv";
const kv = await Deno.openKv();
const federation = createFederation<void>({
kv: new DenoKvStore(kv),
// ... other options
});RedisKvStore
To use the RedisKvStore, you need to install the @fedify/redis package first:
deno add jsr:@fedify/redisnpm add @fedify/redispnpm add @fedify/redisyarn add @fedify/redisbun add @fedify/redisRedisKvStore is a key–value store implementation that uses Redis as the backend storage. It provides scalability and high performance, making it suitable for production use in distributed systems. It requires a Redis server setup and maintenance.
- Best for
- Production use, distributed systems.
- Pros
- Scalable, supports clustering.
- Cons
- Requires Redis setup and maintenance.
import { createFederation } from "@fedify/fedify";
import { RedisKvStore } from "@fedify/redis";
import Redis from "ioredis";
const redis = new Redis(); // Configure as needed
const federation = createFederation<void>({
kv: new RedisKvStore(redis),
// ... other options
});PostgresKvStore
To use the PostgresKvStore, you need to install the @fedify/postgres package first:
deno add jsr:@fedify/postgresnpm add @fedify/postgrespnpm add @fedify/postgresyarn add @fedify/postgresbun add @fedify/postgresPostgresKvStore is a key–value store implementation that uses PostgreSQL as the backend storage. It provides scalability and high performance, making it suitable for production use in distributed systems. It requires a PostgreSQL server setup and maintenance.
- Best for
- Production use, a system that already uses PostgreSQL.
- Pros
- Scalable, no additional setup required if already using PostgreSQL.
- Cons
- Requires PostgreSQL setup and maintenance.
import { createFederation } from "@fedify/fedify";
import { PostgresKvStore } from "@fedify/postgres";
import postgres from "postgres";
const federation = createFederation<void>({
kv: new PostgresKvStore(
postgres("postgresql://user:pass@localhost/db"),
),
// ... other options
});WorkersKvStore (Cloudflare Workers only)
This API is available since Fedify 1.6.0.
WARNING
The @fedify/fedify/x/cfworkers module is deprecated and will be removed in Fedify 2.0.0. Please use the @fedify/cfworkers package instead.
To use the WorkersKvStore, you need to install the @fedify/cfworkers package first:
deno add jsr:@fedify/cfworkersnpm add @fedify/cfworkerspnpm add @fedify/cfworkersyarn add @fedify/cfworkersbun add @fedify/cfworkersWorkersKvStore is a key–value store implementation for Cloudflare Workers that uses Cloudflare's built-in Cloudflare Workers KV API. It provides persistent storage and good performance for Cloudflare Workers environments. It's suitable for production use in Cloudflare Workers applications.
- Best for
- Production use in Cloudflare Workers environments.
- Pros
- Persistent storage, good performance, easy to set up.
- Cons
- Only available in Cloudflare Workers runtime.
import type { Federation } from "@fedify/fedify";
import { WorkersKvStore } from "@fedify/cfworkers";
export default {
async fetch(request, env, ctx) {
const federation: Federation<void> = await builder.build({
kv: new WorkersKvStore(env.KV_BINDING),
});
return await federation.fetch(request, { contextData: undefined });
}
} satisfies ExportedHandler<{ KV_BINDING: KVNamespace<string> }>;NOTE
Since your KVNamespace is not bound to a global variable, but rather passed as an argument to the fetch() method, you need to instantiate your Federation object inside the fetch() method, rather than the top level.
For better organization, you probably want to use a builder pattern to register your dispatchers and listeners before instantiating the Federation object. See the Builder pattern for structuring section for details.
Implementing a custom KvStore
TIP
We are always looking to improve Fedify and add more KvStore implementations. If you've created a custom implementation that you think would be useful to others, consider contributing it to the community by packaging it as a standalone module and sharing it on JSR and npm.
If the provided implementations don't meet your needs, you can create a custom KvStore. Here's a step-by-step guide:
Implement the KvStore interface
Create a class that implements the KvStore interface. The interface defines three methods: get(), set(), delete(), and optionally ~KvStore.cas().
import { KvStore, KvKey, KvStoreSetOptions } from "@fedify/fedify";
class MyCustomKvStore implements KvStore {
async get<T = unknown>(key: KvKey): Promise<T | undefined> {
// Implement get logic
}
async set(
key: KvKey,
value: unknown,
options?: KvStoreSetOptions
): Promise<void> {
// Implement set logic
}
async delete(key: KvKey): Promise<void> {
// Implement delete logic
}
async cas(
key: KvKey,
expectedValue: unknown,
newValue: unknown
): Promise<boolean> {
// Implement compare-and-swap logic if needed
}
}Handle KvKey
The KvKey is an array of strings. You'll need to convert it into a format suitable for your storage backend. For example:
private serializeKey(key: KvKey): string {
return key.join(':');
}
NOTE
The above example uses a simple colon-separated string as the serialized key, but in practice, it probably needs to be more sophisticated to handle complex keys and avoid conflicts.
Implement get() method
Retrieve the value associated with the key. Remember to handle cases where the key doesn't exist:
async get<T = unknown>(key: KvKey): Promise<T | undefined> {
const serializedKey = this.serializeKey(key);
// Retrieve value from your storage backend
const value = await this.storage.retrieve(serializedKey);
return value as T | undefined;
}
Implement set() method
Store the value with the given key. Handle the optional TTL if your backend supports it:
async set(
key: KvKey,
value: unknown,
options?: KvStoreSetOptions,
): Promise<void> {
const serializedKey = this.serializeKey(key);
if (options?.ttl == null) {
await this.storage.set(serializedKey, value);
} else {
// Set with TTL if supported
await this.storage.setWithTtl(
serializedKey,
value,
options.ttl.total('millisecond'),
);
}
}
Implement delete() method
Remove the value associated with the key:
async delete(key: KvKey): Promise<void> {
const serializedKey = this.serializeKey(key);
await this.storage.remove(serializedKey);
}
Implement ~KvStore.cas() method (optional)
If your storage backend supports compare-and-swap (CAS) operations, you can implement the ~KvStore.cas() method. This method allows you to atomically update a value only if it matches the expected value. This is useful for implementing optimistic concurrency control.
async cas(
key: KvKey,
expectedValue: unknown,
newValue: unknown
): Promise<boolean> {
const serializedKey = this.serializeKey(key);
return await this.storage.compareAndSwap(
serializedKey,
expectedValue,
newValue
);
}
Use your custom KvStore
That's it! You can now use your custom KvStore implementation with Fedify:
import { createFederation } from "@fedify/fedify";
const customKvStore = new MyCustomKvStore();
const federation = createFederation<void>({
kv: customKvStore,
// ... other options
});