Deno KV: How the Built-in Key-Value Store Works
Deno KV is an unstable, built-in key-value storage API accessed via Deno.openKv() that provides atomic transactions, queue support, and real-time change watching across both local SQLite and remote HTTP backends.
Deno KV is the native key-value storage solution embedded directly into the Deno runtime. As implemented in the denoland/deno repository, it exposes a JavaScript API that forwards operations to Rust-based storage backends, enabling developers to persist data without external dependencies or switch seamlessly to cloud-hosted storage by changing a single connection string.
What is Deno KV?
Deno KV is exposed through the unstable function Deno.openKv([path]), which returns a Deno.Kv instance capable of storing arbitrary values under composite keys. The system supports binary buffers, strings, numbers, Uint8Array, and bigint values, and provides advanced features including range reads with configurable consistency, atomic write transactions, message queuing, and key change notifications.
Under the hood, Deno KV acts as a thin wrapper around a storage backend implementing the denokv_proto::Database trait. The runtime ships with two production-ready backends: a local SQLite implementation for development and edge scripts, and a Remote client that connects to hosted KV services via the KV Connect protocol.
Architecture and Source Code Implementation
The Deno KV implementation follows a layered architecture that bridges JavaScript and Rust through Deno’s extension system.
JavaScript Entry Point
The public API is defined in ext/kv/01_db.ts, which exports the openKv function and the Kv class. This JavaScript façade forwards all method calls—such as get, set, and atomic()—to native Rust operations registered via the deno_kv extension.
Rust Operations Layer
In ext/kv/lib.rs, the extension registers core operations including op_kv_database_open, op_kv_snapshot_read, op_kv_atomic_write, and op_kv_enqueue. These ops manage database lifecycle, range queries, transactional writes, and queue operations respectively. Each operation retrieves the DatabaseResource from the OpState’s resource table by its ResourceId, then delegates to the underlying backend implementation.
Database Handler Abstraction
The DatabaseHandler trait defined in ext/kv/interface.rs abstracts backend initialization. Concrete implementations SqliteDbHandler and RemoteDbHandler provide the open method, which instantiates backends that satisfy the denokv_proto::Database protocol. This abstraction ensures the public JavaScript API remains identical regardless of whether data is stored locally or remotely.
Storage Backends: SQLite vs. Remote
Deno KV supports two distinct storage backends selected automatically based on the path argument passed to Deno.openKv():
SQLite Backend (denokv_sqlite)
- Implementation:
ext/kv/sqlite.rscreates adenokv_sqlite::Sqliteinstance with Write-Ahead Logging (WAL) enabled for concurrent read/write performance. - Usage: Default when
pathis omitted (uses./kv.sqlite3) or points to a local file. Pass:memory:for ephemeral, in-memory storage ideal for testing. - Characteristics: File-based, zero external dependencies, suitable for local development and edge deployments.
Remote Backend (denokv_remote)
- Implementation:
ext/kv/remote.rsconstructs adenokv_remote::Remoteclient that communicates via HTTP(S) using the KV Connect protocol. - Usage: Activated when
pathis an HTTP(S) URL (e.g.,https://api.deno.com). - Authentication: Reads the
DENO_KV_ACCESS_TOKENenvironment variable for bearer token authentication. - Characteristics: Enables distributed state across multiple runtime instances, requires network permissions.
Core Operations and API Methods
All backends expose a consistent interface through the Deno.Kv class, implemented via the Rust ops in ext/kv/lib.rs.
Basic CRUD Operations
The get, set, and list methods handle single-key reads, writes, and range scans. Range reads support configurable consistency levels and are processed by op_kv_snapshot_read (lines 52-60 in ext/kv/lib.rs), which delegates to the backend’s snapshot_read method.
Atomic Transactions
Atomic operations allow conditional writes using optimistic concurrency control. The kv.atomic() builder constructs transactions that validate versionstamp checks before applying mutations. The op_kv_atomic_write operation (lines 87-95 in ext/kv/lib.rs) executes these transactions with serializable isolation guarantees, ensuring strict ordering across concurrent clients.
Message Queues
Deno KV implements durable queues through enqueue and dequeue methods. The op_kv_enqueue operation persists payloads with delivery deadlines and trigger keys, while op_kv_dequeue_next_message (lines 38-44 in ext/kv/lib.rs) retrieves messages for worker-style processing. Messages must be acknowledged via finish() to confirm successful handling.
Change Watching
The watch method returns a WatchStream that emits notifications when specified keys change. This uses op_kv_watch and op_kv_watch_next (lines 75-85 in ext/kv/lib.rs) to maintain a persistent connection to backend event sources, enabling real-time reactive applications.
Configuration and Operational Limits
Storage constraints are enforced via KvConfig defined in ext/kv/config.rs (lines 3-15). This configuration specifies maximum key sizes, value sizes, and limits on the number of range entries and mutations per transaction. The runtime consults these limits during every operation to prevent resource exhaustion.
Practical Usage Examples
Opening a Local Database
// Open default SQLite database (./kv.sqlite3)
const db = await Deno.openKv();
// Store a complex object under a composite key
await db.set(["users", 123], { name: "Alice", active: true });
const entry = await db.get(["users", 123]);
console.log(entry?.value); // { name: "Alice", active: true }
In-Memory Testing
// Create ephemeral database for unit tests
const kv = await Deno.openKv(":memory:");
await kv.set(["temp"], "data");
Atomic Counter Increment
const kv = await Deno.openKv(":memory:");
await kv.set(["counter"], 0);
const result = await kv.atomic()
.check({ key: ["counter"], versionstamp: (await kv.get(["counter"])).versionstamp })
.mutate({ key: ["counter"], type: "sum", value: 5 })
.commit();
if (result.ok) {
console.log("Increment succeeded");
} else {
console.log("Concurrent modification detected");
}
Enqueuing Background Tasks
const kv = await Deno.openKv();
// Schedule payload with 60-second deadline, triggered by "tasks" key
await kv.enqueue(
new Uint8Array([1, 2, 3]),
60000,
[["tasks"]],
);
// Worker loop
while (true) {
const msg = await kv.dequeue("tasks");
if (msg) {
console.log("Processing:", msg.payload);
await msg.finish(true); // Acknowledge completion
}
}
Watching Configuration Changes
const kv = await Deno.openKv();
const watcher = await kv.watch(["config", "featureFlag"]);
for await (const change of watcher) {
console.log("Flag updated:", change.value);
}
Summary
- Deno KV is an unstable, embedded key-value store accessed via
Deno.openKv()in the Deno runtime. - The architecture separates JavaScript API surface (
ext/kv/01_db.ts) from Rust implementation (ext/kv/lib.rs) through thedeno_kvextension system. - Two backends provide deployment flexibility: SQLite for local/edge storage (
ext/kv/sqlite.rs) and Remote for distributed cloud storage (ext/kv/remote.rs). - Atomic transactions with versionstamp checks ensure serializable consistency across concurrent operations via
op_kv_atomic_write. - Built-in queuing supports durable message delivery with
enqueueanddequeueoperations backed by the same storage layer. - Change watching enables real-time subscriptions to key modifications through async iterators.
Frequently Asked Questions
How do I enable Deno KV in my application?
Deno KV currently requires the --unstable flag when running the Deno runtime because the API is not yet finalized. Import the Deno namespace and call await Deno.openKv() to receive a Deno.Kv instance. No additional packages or external dependencies are required.
What is the difference between SQLite and Remote backends?
The SQLite backend stores data in a local file using Write-Ahead Logging, making it ideal for development, testing, and single-node edge deployments. The Remote backend connects to a hosted KV service via HTTP using the KV Connect protocol, enabling data persistence across multiple distributed instances. Switching between them requires only changing the path argument from a file path to an HTTPS URL.
Are there limits on key or value sizes in Deno KV?
Yes. The KvConfig struct in ext/kv/config.rs defines operational limits including maximum key size, maximum value size, and constraints on the number of entries returned in range queries or modified in atomic transactions. These limits prevent abuse and ensure consistent performance across both SQLite and Remote backends.
How does Deno KV handle concurrent write conflicts?
Deno KV implements optimistic concurrency control using versionstamps. When performing an atomic write via kv.atomic().check().mutate().commit(), the operation validates that the key’s versionstamp matches the expected value before applying mutations. If another writer modified the key concurrently, the check fails and the transaction returns ok: false, allowing the application to retry or resolve the conflict.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →