On this page
Deno KV Quick Start
Deno KV is a
key-value database
built directly into the Deno runtime, available in the
Deno.Kv
namespace. It can be used
for many kinds of data storage use cases, but excels at storing simple data
structures that benefit from very fast reads and writes. Deno KV is available in
the Deno CLI and on Deno Deploy.
Let's walk through the key features of Deno KV.
Opening a database Jump to heading
In your Deno program, you can get a reference to a KV database using
Deno.openKv()
. You may pass in
an optional file system path to where you'd like to store your database,
otherwise one will be created for you based on the current working directory of
your script.
const kv = await Deno.openKv();
Creating, updating, and reading a key-value pair Jump to heading
Data in Deno KV is stored as key-value pairs, much like properties of a
JavaScript object literal or a
Map.
Keys are represented as an array of JavaScript types, like
string
, number
, bigint
, or boolean
. Values can be arbitrary JavaScript
objects. In this example, we create a key-value pair representing a user's UI
preferences, and save it with
kv.set()
.
const kv = await Deno.openKv();
const prefs = {
username: "ada",
theme: "dark",
language: "en-US",
};
const result = await kv.set(["preferences", "ada"], prefs);
Once a key-value pair is set, you can read it from the database with
kv.get()
:
const entry = await kv.get(["preferences", "ada"]);
console.log(entry.key);
console.log(entry.value);
console.log(entry.versionstamp);
Both get
and list
operations return a
KvEntry object with the
following properties:
key
- the array key you used to set the valuevalue
- the JavaScript object you set for this keyversionstamp
- a generated value used to determine if a key has been updated.
The set
operation is also used to update objects that already exist for a
given key. When a key's value is updated, its versionstamp
will change to a
new generated value.
Listing several key-value pairs Jump to heading
To get values for a finite number of keys, you may use
kv.getMany()
.
Pass in several keys as arguments, and you'll receive an array of values for
each key. Note that values and versionstamps can be null
if no value
exists for the given key(s).
const kv = await Deno.openKv();
const result = await kv.getMany([
["preferences", "ada"],
["preferences", "grace"],
]);
result[0].key; // ["preferences", "ada"]
result[0].value; // { ... }
result[0].versionstamp; // "00000000000000010000"
result[1].key; // ["preferences", "grace"]
result[1].value; // null
result[1].versionstamp; // null
Often, it is useful to retrieve a list of key-value pairs from all keys that
share a given prefix. This type of operation is possible using
kv.list()
. In this
example, we get a list of key-value pairs that share the "preferences"
prefix.
const kv = await Deno.openKv();
const entries = kv.list({ prefix: ["preferences"] });
for await (const entry of entries) {
console.log(entry.key); // ["preferences", "ada"]
console.log(entry.value); // { ... }
console.log(entry.versionstamp); // "00000000000000010000"
}
Returned keys are ordered lexicographically based on the next component of the key after the prefix. So KV pairs with these keys:
["preferences", "ada"]
["preferences", "bob"]
["preferences", "cassie"]
Will be returned in that order by kv.list()
.
Read operations can either be performed in strong or eventual consistency mode. Strong consistency mode guarantees that the read operation will return the most recently written value. Eventual consistency mode may return a stale value, but is faster. By contrast, writes are always performed in strong consistency mode.
Deleting key-value pairs Jump to heading
You can delete a key from the database using
kv.delete()
. No
action is taken if no value is found for the given key.
const kv = await Deno.openKv();
await kv.delete(["preferences", "alan"]);
Atomic transactions Jump to heading
Deno KV is capable of executing atomic transactions, which enables you to conditionally execute one or many data manipulation operations at once. In the following example, we create a new preferences object only if it hasn't been created already.
const kv = await Deno.openKv();
const key = ["preferences", "alan"];
const value = {
username: "alan",
theme: "light",
language: "en-GB",
};
const res = await kv.atomic()
.check({ key, versionstamp: null }) // `null` versionstamps mean 'no value'
.set(key, value)
.commit();
if (res.ok) {
console.log("Preferences did not yet exist. Inserted!");
} else {
console.error("Preferences already exist.");
}
Learn more about transactions in Deno KV here.
Improve querying with secondary indexes Jump to heading
Secondary indexes store the same data by multiple keys, allowing for simpler queries of the data you need. Let's say that we need to be able to access user preferences by both username AND email. To enable this, you could provide a function that wraps the logic to save the preferences to create two indexes.
const kv = await Deno.openKv();
async function savePreferences(prefs) {
const key = ["preferences", prefs.username];
// Set the primary key
const r = await kv.set(key, prefs);
// Set the secondary key's value to be the primary key
await kv.set(["preferencesByEmail", prefs.email], key);
return r;
}
async function getByUsername(username) {
// Use as before...
const r = await kv.get(["preferences", username]);
return r;
}
async function getByEmail(email) {
// Look up the key by email, then second lookup for actual data
const r1 = await kv.get(["preferencesByEmail", email]);
const r2 = await kv.get(r1.value);
return r2;
}
Learn more about secondary indexes in the manual here.
Watching for updates in Deno KV Jump to heading
You can also listen for updates from Deno KV with kv.watch()
, which will emit
a new value or values of the key or keys you provide. In the below chat example,
we watch for updates on the key ["last_message_id", roomId]
. We retrieve
messageId
, which we then use with kv.list()
to grab all the new messages
from seen
and messageId
.
let seen = "";
for await (const [messageId] of kv.watch([["last_message_id", roomId]])) {
const newMessages = await Array.fromAsync(kv.list({
start: ["messages", roomId, seen, ""],
end: ["messages", roomId, messageId, ""],
}));
await websocket.write(JSON.stringify(newMessages));
seen = messageId;
}
Learn more about using Deno KV watch here.
Production usage Jump to heading
Deno KV is available for use in live applications on Deno Deploy. In production, Deno KV is backed by FoundationDB, the open source key-value store created by Apple.
No additional configuration is necessary to run your Deno programs that use KV on Deploy - a new Deploy database will be provisioned for you when required by your code. Learn more about Deno KV on Deno Deploy here.
Testing Jump to heading
By default, Deno.openKv()
creates or opens a persistent store based on the path from which the script that
invoked it was run. This isn't usually desirable for tests, which need to
produce the same behavior when run many times in a row.
To test code that uses Deno KV, you can use the special argument ":memory:"
to
create an ephemeral Deno KV datastore.
async function setDisplayName(
kv: Deno.Kv,
username: string,
displayname: string,
) {
await kv.set(["preferences", username, "displayname"], displayname);
}
async function getDisplayName(
kv: Deno.Kv,
username: string,
): Promise<string | null> {
return (await kv.get(["preferences", username, "displayname"]))
.value as string;
}
Deno.test("Preferences", async (t) => {
const kv = await Deno.openKv(":memory:");
await t.step("can set displayname", async () => {
const displayName = await getDisplayName(kv, "example");
assertEquals(displayName, null);
await setDisplayName(kv, "example", "Exemplary User");
const displayName = await getDisplayName(kv, "example");
assertEquals(displayName, "Exemplary User");
});
});
This works because Deno KV is backed by SQLite when run for local development. Just like in-memory SQLite databases, multiple ephemeral Deno KV stores can exist at once without interfering with one another. For more information about special database addressing modes, see the SQLite docs on the topic.
Next steps Jump to heading
At this point, you're just beginning to scratch the surface with Deno KV. Be sure to check out our guide on the Deno KV key space, and a collection of tutorials and example applications here.