KV Storage

Nitro provides a built-in storage layer that can abstract the filesystem, a database, or any other data source.

Nitro has built-in integration with unstorage to provide a runtime-agnostic persistence layer: your code always calls the same key-value API, and the backing driver (memory, filesystem, Redis, ...) is swapped through configuration.

#Usage

To use the storage layer, you can use the useStorage() utility to access the storage instance.

import { useStorage } from "nitro/storage";

// Default storage (in-memory)
await useStorage().setItem("test:foo", { hello: "world" });
const value = await useStorage().getItem("test:foo");

// You can specify a base prefix with useStorage(base)
const testStorage = useStorage("test");
await testStorage.setItem("foo", { hello: "world" });
await testStorage.getItem("foo"); // { hello: "world" }

// You can use generics to type the return value
await useStorage<{ hello: string }>("test").getItem("foo");
await useStorage("test").getItem<{ hello: string }>("foo");
Read more in unstorage.unjs.io.

#Available methods

The storage instance returned by useStorage() provides the following methods:

MethodDescription
getItem(key)Get the value of a key. Returns null if the key does not exist.
getItems(items)Get multiple items at once. Accepts an array of keys or { key, options } objects.
getItemRaw(key)Get the raw value of a key without parsing. Useful for binary data.
setItem(key, value)Set the value of a key.
setItems(items)Set multiple items at once. Accepts an array of { key, value } objects.
setItemRaw(key, value)Set the raw value of a key without serialization.
hasItem(key)Check if a key exists. Returns a boolean.
removeItem(key)Remove a key from storage.
getKeys(base?)Get all keys, optionally filtered by a base prefix.
clear(base?)Clear all keys, optionally filtered by a base prefix.
getMeta(key)Get metadata for a key (e.g., mtime, atime, ttl).
setMeta(key, meta)Set metadata for a key.
removeMeta(key)Remove metadata for a key.
mount(base, driver)Dynamically mount a storage driver at a base path.
unmount(base)Unmount a storage driver from a base path.
watch(callback)Watch for changes. Callback receives (event, key) where event is "update" or "remove".
unwatch()Stop watching for changes.

Shorthand aliases are also available: get, set, has, del, remove, keys.

import { useStorage } from "nitro/storage";

// Get all keys under a prefix
const keys = await useStorage("test").getKeys();

// Check if a key exists
const exists = await useStorage().hasItem("test:foo");

// Remove a key
await useStorage().removeItem("test:foo");

// Get raw binary data
const raw = await useStorage().getItemRaw("assets:server:image.png");

// Get metadata (type, etag, mtime, etc.)
const meta = await useStorage("assets:server").getMeta("file.txt");

#Configuration

You can mount one or multiple custom storage drivers using the storage option.

The key is the mount point name, and the value is the driver name and configuration.

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  storage: {
    redis: {
      driver: "redis",
      /* redis connector options */
    }
  }
})

Then, you can use the redis storage using the useStorage("redis") function.

Read more in unstorage.unjs.io/.

#Driver dependencies

Some drivers rely on a third-party library (for example, redis requires ioredis).

Nitro detects the libraries required by the mounted drivers and prompts to install the missing ones (installed automatically in CI). Installed libraries are then explicitly passed to the driver via its lib option, so that bundlers can statically resolve them.

#Development storage

You can use the devStorage option to override storage configuration during development and prerendering.

This is useful when your production driver is not available in development (e.g., a managed Redis instance).

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  storage: {
    db: {
      driver: "redis",
      host: "prod.example.com",
    }
  },
  devStorage: {
    db: {
      driver: "fs",
      base: "./.data/db"
    }
  }
})

When running in development mode, devStorage mounts are merged on top of storage mounts, allowing you to use a local filesystem driver or an in-memory driver while developing.

Tip

A common setup is a hosted driver (e.g. redis) in storage for production and an fs driver in devStorage, so development data is easy to inspect locally in .data/ and does not require external services.

#Built-in mount points

Nitro automatically mounts the following storage paths:

#/assets

Server assets are mounted at the /assets base path (see Server assets). In production, this mount is read-only and only supports getKeys(), hasItem(), getItem(), getItemRaw(), and getMeta().

import { useStorage } from "nitro/storage";

// Access server assets via the /assets mount
const content = await useStorage("assets:server").getItem("my-file.txt");

#Default (in-memory)

The root storage (without a base path) uses an in-memory driver by default. Data stored here is not persisted across restarts.

import { useStorage } from "nitro/storage";

// In-memory by default, not persisted
await useStorage().setItem("counter", 1);

To persist data, mount a driver with a persistent backend (e.g., fs, redis, etc.) using the storage configuration option.

Note

The cache layer also stores its entries in this root storage (under the cache: prefix) unless you configure a dedicated cache mount point, so cached data is not persisted across restarts by default either.

#Server assets

Nitro allows you to bundle files from an assets/ directory. These files are accessible at runtime via the assets:server storage mount.

The directory is resolved relative to serverDir when it is set, and to the project root otherwise:

my-project/
  server/
    assets/
      data.json
      templates/
        welcome.html
    routes/
      index.ts
server/routes/index.ts
import { defineHandler } from "nitro";
import { useStorage } from "nitro/storage";

export default defineHandler(async () => {
  const serverAssets = useStorage("assets:server");

  const keys = await serverAssets.getKeys();
  const data = await serverAssets.getItem("data.json");
  const template = await serverAssets.getItem("templates/welcome.html");

  return { keys, data, template };
});

#Custom asset directories

You can register additional asset directories using the serverAssets config option. Each dir is resolved relative to rootDir (not serverDir):

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  serverAssets: [
    {
      baseName: "templates",
      dir: "./templates",
    }
  ]
})

Custom asset directories are accessible under assets:<baseName>:

import { useStorage } from "nitro/storage";

const templates = useStorage("assets:templates");
const keys = await templates.getKeys();
const html = await templates.getItem("email.html");

#Asset metadata

Server assets include metadata such as content type, ETag, and modification time:

import { useStorage } from "nitro/storage";

const serverAssets = useStorage("assets:server");

const meta = await serverAssets.getMeta("image.png");
// { type: "image/png", etag: "\"...\"", mtime: "2024-01-01T00:00:00.000Z" }

// Useful for setting response headers
const raw = await serverAssets.getItemRaw("image.png");

Note

In development (and while prerendering), server assets are read directly from the filesystem with the fs driver, so getMeta() returns filesystem stats (mtime, size, ...) instead of type and etag. In production, assets are bundled into the build output with their metadata pre-computed at build time.

Read more in Docs > Assets.

#Runtime configuration

In scenarios where the mount point configuration is not known until runtime, Nitro can dynamically add mount points during startup using plugins.

plugins/storage.ts
import { useStorage } from "nitro/storage";
import { definePlugin } from "nitro";
import redisDriver from "unstorage/drivers/redis";

export default definePlugin(() => {
  const storage = useStorage()

  // Dynamically pass in credentials from runtime configuration, or other sources
  const driver = redisDriver({
    base: "redis",
    host: process.env.REDIS_HOST,
    port: Number(process.env.REDIS_PORT),
    // Pass the driver library explicitly so that it is bundled
    lib: () => import("ioredis"),
    /* other redis connector options */
  })

  // Mount driver
  storage.mount("redis", driver)
})

Important

Drivers mounted this way are outside of Nitro's driver dependency detection: install the third-party library yourself (ioredis in the example above) and pass it via the driver's lib option. Without it, the driver falls back to a dynamic import("ioredis") that bundlers cannot statically resolve, and the mount fails at runtime with a Cannot import ioredis error.

Nitro  builds full-stack servers that deploy anywhere.