Keyv is a thin API over a store. Performance is almost entirely the backend's; Keyv adds namespacing, encoding, TTL conversion, and a consistent Promise API.
Table of Contents
Official adapters
| Backend | Package | Native expiry | Notes |
|---|---|---|---|
| Memory / Map / LRU | built-in KeyvMemoryAdapter |
Yes (lazy) | Default. See Using Map and LRU |
| Redis | @keyv/redis | Yes (PXAT, PX fallback) |
Clusters, Sentinel, TLS |
| Valkey | @keyv/valkey | Yes (PXAT) |
Redis-compatible OSS |
| MongoDB | @keyv/mongo | TTL index (lazy sweep) | Revalidated in Keyv |
| SQLite | @keyv/sqlite | expires column |
Node, better-sqlite3, bun:sqlite |
| PostgreSQL | @keyv/postgres | expires column |
|
| MySQL | @keyv/mysql | expires column |
Interval sweeper |
| Etcd | @keyv/etcd | Leases | |
| Memcache | @keyv/memcache | Seconds (exptime) |
Keyv checkExpired keeps ms precision |
| DynamoDB | @keyv/dynamo | TTL attribute (hours lag) | |
| Cloudflare KV | @keyv/cloudflare-kv | Client-side + 60s native min | Worker bind or REST |
| BigMap | @keyv/bigmap | Via Keyv | Scale past ~16.7M Map entries |
Install the adapter next to keyv and pass an instance as store (or as the first constructor argument):
import Keyv from "keyv";
import KeyvRedis from "@keyv/redis";
const keyv = new Keyv(new KeyvRedis("redis://localhost:6379"), {
namespace: "cache",
});
Many adapters also export createKeyv(...) so you can skip the two-step construct.
How to use
- Install
keyvand the adapter package. - Construct the adapter (URI, native client, or options).
- Pass it to
new Keyv(adapter)or{ store }. - Listen for
'error'on the Keyv instance. - Use
set/get/deleteas usual. TTL onkeyv.setis relative milliseconds; Keyv converts to absoluteexpiresfor v6 adapters.
keyv.on("error", (error) => console.error(error));
await keyv.set("foo", { n: 1 }, 60_000);
await keyv.get("foo");
Map-like stores do not need an extra package:
import QuickLRU from "quick-lru";
const keyv = new Keyv({ store: new QuickLRU({ maxSize: 1000 }) });
Benchmarks
Keyv itself is not the bottleneck. Compare drivers and runtimes, not Keyv vs Keyv.
SQLite (in-memory, 10k pre-generated pairs, set then get). From the @keyv/sqlite suite — relative numbers, not a guarantee on your machine:
| Driver | Summary | ops/sec |
|---|---|---|
| bun:sqlite | fastest in that run | ~64K |
| better-sqlite3 | ~32% slower | ~44K |
| node:sqlite | ~33% slower | ~43K |
| sqlite3 (legacy) | ~75% slower | ~16K |
BigMap vs Map: native Map is faster below the ~16.7 million entry limit. @keyv/bigmap exists to go beyond that limit by hashing across inner Maps.
For Redis, Postgres, and the rest, measure your deployment (network RTT, pipeline, cluster). Use getMany / setMany when the adapter implements them natively.
Build your own
Implement KeyvStorageAdapter. Required: async get, set, delete, clear. Optional but recommended: has, getMany, setMany, deleteMany, hasMany, iterator, disconnect.
v6 set takes absolute expires (Unix ms). Declare it:
import { keyvStorageCapability, type KeyvStorageEntry } from "keyv";
class MyAdapter {
get capabilities() {
return keyvStorageCapability(this);
}
async set(key, value, expires) {
/* persist value + expires */
return true;
}
async setMany(entries: KeyvStorageEntry[]) {
/* ... */
}
}
If you omit capabilities.expires, Keyv wraps you in KeyvBridgeAdapter and converts expires back to relative ttl. See Legacy Storage Adapters.
Test with @keyv/test-suite:
import { keyvTestSuite, storageTestSuite } from "@keyv/test-suite";
import { Keyv } from "keyv";
import { test } from "vitest";
import MyAdapter from "./my-adapter.js";
const store = () => new MyAdapter();
keyvTestSuite(test, Keyv, store);
storageTestSuite(test, store);
Community adapters are listed on Third-Party Adapters.