Skip to main content

unstorage vs keyv vs cache-manager: Universal Key-Value Storage in Node.js (2026)

·PkgPulse Team

TL;DR

unstorage (by UnJS) is the modern universal storage layer — driver-based API supporting memory, filesystem, Redis, Cloudflare KV, Vercel KV, HTTP, and more. keyv is the simple key-value store with TTL — adapters for Redis, Postgres, SQLite, MongoDB, and other backends. cache-manager is the caching-focused library — multi-level caches (memory → Redis), TTL, tag-based invalidation, and wrapping. In 2026: unstorage for multi-driver storage in modern frameworks (Nuxt, Nitro), keyv for simple persistent key-value needs, cache-manager for multi-tier caching strategies.

Key Takeaways

  • unstorage: ~3M weekly downloads — UnJS ecosystem, powers Nuxt/Nitro storage, 20+ drivers
  • keyv: ~10M weekly downloads — simple get/set/delete with TTL, adapter-based backends
  • cache-manager: ~1M weekly downloads — multi-tier caching, wrap pattern, tag invalidation
  • unstorage supports getItem, setItem, getKeys, watch — filesystem-like API
  • keyv is the simplest — await kv.set("key", value, ttl) / await kv.get("key")
  • cache-manager's wrap() pattern: check cache → miss → compute → store → return

unstorage

unstorage — universal storage layer:

Basic usage

import { createStorage } from "unstorage"

// In-memory (default):
const storage = createStorage()

await storage.setItem("package:react", {
  name: "react",
  downloads: 5_000_000,
  score: 92.5,
})

const pkg = await storage.getItem("package:react")
// → { name: "react", downloads: 5000000, score: 92.5 }

// List keys:
const keys = await storage.getKeys("package:")
// → ["package:react", "package:vue", ...]

// Check existence:
const exists = await storage.hasItem("package:react")  // true

// Delete:
await storage.removeItem("package:react")

Drivers

import { createStorage } from "unstorage"
import fsDriver from "unstorage/drivers/fs"
import redisDriver from "unstorage/drivers/redis"
import cloudflareKVBindingDriver from "unstorage/drivers/cloudflare-kv-binding"

// Filesystem:
const fsStorage = createStorage({
  driver: fsDriver({ base: "./data" }),
})

// Redis:
const redisStorage = createStorage({
  driver: redisDriver({
    url: process.env.REDIS_URL,
    ttl: 3600,  // Default TTL: 1 hour
  }),
})

// Cloudflare KV (in Workers):
const kvStorage = createStorage({
  driver: cloudflareKVBindingDriver({ binding: "MY_KV" }),
})

Mount points (multi-driver)

import { createStorage } from "unstorage"
import memoryDriver from "unstorage/drivers/memory"
import redisDriver from "unstorage/drivers/redis"
import fsDriver from "unstorage/drivers/fs"

const storage = createStorage()

// Mount different drivers at different paths:
storage.mount("cache:", redisDriver({ url: process.env.REDIS_URL }))
storage.mount("data:", fsDriver({ base: "./data" }))
storage.mount("temp:", memoryDriver())

// Access transparently:
await storage.setItem("cache:package:react", { score: 92 })  // → Redis
await storage.setItem("data:config", { theme: "dark" })       // → Filesystem
await storage.setItem("temp:session:abc", { userId: 123 })    // → Memory

Watch for changes

// Watch for storage changes:
const unwatch = await storage.watch((event, key) => {
  console.log(`${event}: ${key}`)
  // "update:package:react"
  // "remove:package:vue"
})

keyv

keyv — simple key-value with TTL:

Basic usage

import Keyv from "keyv"

// In-memory (default):
const kv = new Keyv()

// Set with TTL (milliseconds):
await kv.set("package:react", { name: "react", score: 92 }, 3600_000)  // 1 hour

// Get:
const pkg = await kv.get("package:react")
// → { name: "react", score: 92 } (or undefined if expired)

// Delete:
await kv.delete("package:react")

// Clear all:
await kv.clear()

// Check if key exists:
const exists = await kv.has("package:react")

Storage adapters

import Keyv from "keyv"

// Redis:
const kv = new Keyv("redis://localhost:6379")

// PostgreSQL:
const kv = new Keyv("postgresql://user:pass@localhost:5432/db")

// SQLite:
const kv = new Keyv("sqlite://./data/cache.sqlite")

// MongoDB:
const kv = new Keyv("mongodb://localhost:27017/cache")

// With options:
const kv = new Keyv({
  uri: "redis://localhost:6379",
  namespace: "packages",    // Key prefix: "packages:key"
  ttl: 3600_000,            // Default TTL: 1 hour
  serialize: JSON.stringify,
  deserialize: JSON.parse,
})

Namespaces

// Separate data by namespace:
const packageCache = new Keyv({ namespace: "packages" })
const userCache = new Keyv({ namespace: "users" })
const sessionStore = new Keyv({ namespace: "sessions", ttl: 86400_000 })

await packageCache.set("react", { score: 92 })
await userCache.set("user-123", { name: "Royce" })

// Keys in Redis:
// "packages:react"
// "users:user-123"
// No collision between namespaces

As HTTP cache

import Keyv from "keyv"
import KeyvRedis from "@keyv/redis"
import http from "node:http"

const cache = new Keyv({ store: new KeyvRedis("redis://localhost:6379") })

// Cache-aside pattern:
async function getCachedPackage(name: string) {
  const cached = await cache.get(`pkg:${name}`)
  if (cached) return cached

  const data = await fetchFromNpm(name)
  await cache.set(`pkg:${name}`, data, 300_000)  // Cache 5 minutes
  return data
}

cache-manager

cache-manager — multi-tier caching:

Basic setup

import { caching } from "cache-manager"

// In-memory cache:
const cache = await caching("memory", {
  max: 1000,          // Max 1000 items
  ttl: 60 * 1000,     // Default TTL: 60 seconds
})

// Set / Get:
await cache.set("package:react", { name: "react", score: 92 })
const pkg = await cache.get("package:react")

// Delete:
await cache.del("package:react")

// Reset:
await cache.reset()

The wrap() pattern (cache-aside)

import { caching } from "cache-manager"

const cache = await caching("memory", { ttl: 300_000 })  // 5 min TTL

// wrap() = check cache → miss → compute → store → return:
async function getPackageScore(name: string): Promise<number> {
  return cache.wrap(`score:${name}`, async () => {
    // This only runs on cache miss:
    console.log(`Cache miss — computing score for ${name}`)
    const data = await fetchFromNpm(name)
    return calculateScore(data)
  })
}

// First call: cache miss → fetches and computes → stores → returns
await getPackageScore("react")  // ~500ms

// Second call: cache hit → returns immediately
await getPackageScore("react")  // ~1ms

Multi-tier caching (memory → Redis)

import { caching, multiCaching } from "cache-manager"
import { redisStore } from "cache-manager-ioredis-yet"

// Tier 1: Memory (fast, limited):
const memoryCache = await caching("memory", {
  max: 500,
  ttl: 30_000,  // 30 seconds
})

// Tier 2: Redis (slower, larger):
const redisCache = await caching(redisStore, {
  host: "localhost",
  port: 6379,
  ttl: 300_000,  // 5 minutes
})

// Multi-tier: checks memory first, then Redis:
const cache = multiCaching([memoryCache, redisCache])

// Read: memory → Redis → compute
// Write: stores in BOTH tiers
const score = await cache.wrap("score:react", async () => {
  return calculateScore("react")
})

Tag-based invalidation

import { caching } from "cache-manager"

const cache = await caching("memory", { ttl: 600_000 })

// Store with tags:
await cache.set("package:react", data, { tags: ["packages", "frontend"] })
await cache.set("package:vue", data, { tags: ["packages", "frontend"] })
await cache.set("package:express", data, { tags: ["packages", "backend"] })

// Invalidate all "frontend" packages:
await cache.store.tags?.invalidate(["frontend"])
// react and vue caches cleared, express still cached

Feature Comparison

Featureunstoragekeyvcache-manager
Multi-driver✅ (20+ drivers)✅ (adapters)✅ (stores)
TTL support✅ (driver-level)
Namespaces✅ (mount points)
Multi-tier cache
wrap() pattern
Tag invalidation
Watch/subscribe
Edge runtime✅ (CF KV, Vercel)
TypeScript
Weekly downloads~3M~10M~1M

When to Use Each

Choose unstorage if:

  • Building with Nuxt/Nitro — unstorage is the native storage layer
  • Need multiple storage backends mounted at different paths
  • Edge runtime support (Cloudflare KV, Vercel KV, Deno KV)
  • Want filesystem-like API (getItem, setItem, getKeys)

Choose keyv if:

  • Simple key-value storage with TTL — minimal API
  • Need namespaced data across Redis, Postgres, SQLite, or MongoDB
  • Lightweight — zero config for in-memory, one URL for backends
  • Just need get/set/delete with expiration

Choose cache-manager if:

  • Multi-tier caching strategy (memory → Redis → database)
  • The wrap() pattern is your primary caching pattern
  • Need tag-based cache invalidation
  • Building an API that benefits from layered cache architecture

Methodology

Download data from npm registry (weekly average, February 2026). Feature comparison based on unstorage v1.x, keyv v5.x, and cache-manager v5.x.

Compare caching, storage, and key-value packages on PkgPulse →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.