Storage Adapters

Official backends, native TTL, how to use an adapter, benchmarks, and writing your own.

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

  1. Install keyv and the adapter package.
  2. Construct the adapter (URI, native client, or options).
  3. Pass it to new Keyv(adapter) or { store }.
  4. Listen for 'error' on the Keyv instance.
  5. Use set / get / delete as usual. TTL on keyv.set is relative milliseconds; Keyv converts to absolute expires for 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.

Edit this page