KV 存储

Nitro 提供了一个内置存储层,可以抽象文件系统、数据库或任何其他数据源。

Nitro 内置了与 unstorage 的集成,以提供与运行时无关的持久化层:你的代码始终调用相同的键值 API,而底层驱动(内存、文件系统、Redis 等)则通过配置进行切换。

#用法

要使用存储层,可以使用 useKV() 工具函数访问存储实例。

import { useKV } from "nitro/kv";

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

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

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

Note

useKV 之前名为 useStorage,并从 nitro/storage 导出;kv 配置选项之前名为 storage。旧名称仍可作为已弃用的别名使用。已弃用的 devStorage 选项已被 $development(以及 $prerender)中的 kv 替代。

#可用方法

useKV() 返回的存储实例提供以下方法:

方法描述
getItem(key)获取键的值。如果键不存在则返回 null。
getItems(items)一次获取多个项目。接受键数组或 { key, options } 对象。
getItemRaw(key)获取键的原始值,不进行解析。适用于二进制数据。
setItem(key, value)设置键的值。
setItems(items)一次设置多个项目。接受 { key, value } 对象数组。
setItemRaw(key, value)设置键的原始值,不进行序列化。
hasItem(key)检查键是否存在。返回布尔值。
removeItem(key)从存储中移除键。
getKeys(base?)获取所有键,可选按基础前缀过滤。
clear(base?)清除所有键,可选按基础前缀过滤。
getMeta(key)获取键的元数据(例如 mtime、atime、ttl)。
setMeta(key, meta)设置键的元数据。
removeMeta(key)移除键的元数据。
mount(base, driver)在基础路径动态挂载存储驱动。
unmount(base)从基础路径卸载存储驱动。
watch(callback)监视变更。回调接收 (event, key),其中 event 为 "update" 或 "remove"。
unwatch()停止监视变更。

还提供了简写别名:get、set、has、del、remove、keys。

import { useKV } from "nitro/kv";

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

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

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

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

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

#配置

你可以使用 kv 选项挂载一个或多个自定义存储驱动。

键是挂载点名称,值是驱动名称和配置。

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

export default defineConfig({
  kv: {
    redis: {
      driver: "redis",
      /* redis 连接器选项 */
    }
  }
})

然后,你可以使用 useKV("redis") 函数来使用 Redis 存储。

Read more in unstorage.unjs.io/.

Important

挂载选项会被序列化到构建输出中,因此必须是可进行 JSON 序列化的。如果挂载需要不可序列化的选项,请使用运行时配置。

#驱动依赖

某些驱动依赖第三方库(例如,redis 需要 ioredis)。

Nitro 会检测挂载驱动所需的库,并提示安装缺失的库(在 CI 中会自动安装)。安装的库随后会通过其 lib 选项显式传递给驱动,以便打包器能够静态解析它们。因此,lib 选项无法在 kv 挂载中配置(将其设置为 null 可选择不注入导入)。

#开发存储

你可以使用 $development 配置键在开发期间覆盖 kv 挂载。

当你的生产驱动在开发环境中不可用时(例如托管的 Redis 实例),这很有用。

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

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

在开发模式下运行时,$development.kv 挂载会合并到 kv 挂载之上,让你在开发时可以使用本地文件系统驱动或内存驱动。使用不同 driver 的挂载会替换整个挂载(上例中的 host 不会传递给 fs 驱动)。使用相同 driver 的挂载则会深度合并其选项。

$development 仅适用于开发服务器。在预渲染生产构建时,则会依次应用 $production 和 $prerender 覆盖项。使用 $prerender 仅在预渲染期间覆盖挂载:

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

export default defineConfig({
  kv: {
    db: { driver: "redis", host: "prod.example.com" }
  },
  $development: {
    kv: { db: { driver: "fs", base: "./.data/db" } }
  },
  $prerender: {
    kv: { db: { driver: "fs", base: "./.data/db" } }
  }
})

Tip

一种常见配置是在生产环境的 kv 中使用托管驱动(例如 redis),并在 $development.kv 中使用 fs 驱动,这样开发数据便于在本地 .data/ 中检查,也无需依赖外部服务。

#内置挂载点

Nitro 自动挂载以下存储路径:

#/assets

服务器资源挂载在 /assets 基础路径下(参见服务器资源)。在生产环境中,此挂载为只读,并且仅支持 getKeys()、hasItem()、getItem()、getItemRaw() 和 getMeta()。

import { useKV } from "nitro/kv";

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

#默认(内存中)

根存储(无基础路径)默认使用内存驱动。此处存储的数据在重启后不会持久化。

import { useKV } from "nitro/kv";

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

要持久化数据,请使用 kv 配置选项挂载具有持久化后端的驱动(例如 fs、redis 等)。

Note

缓存层也会将其条目存储在此根存储中(位于 cache: 前缀下),除非你配置了专用的 cache 挂载点,因此默认情况下,缓存数据在重启后也不会持久化。

#服务器资源

Nitro 允许你从 assets/ 目录中捆绑文件。这些文件可以在运行时通过 assets:server 存储挂载访问。

当设置了 serverDir 时,目录相对于该目录解析;否则相对于项目根目录解析:

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

export default defineHandler(async () => {
  const serverAssets = useKV("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 };
});

#自定义资源目录

你可以使用 serverAssets 配置选项注册其他资源目录。每个 dir 都相对于 rootDir(而不是 serverDir)解析:

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

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

自定义资源目录可通过 assets:<baseName> 访问:

import { useKV } from "nitro/kv";

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

#资源元数据

服务器资源包含元数据,例如内容类型、ETag 和修改时间:

import { useKV } from "nitro/kv";

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

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

// 适用于设置响应头
const raw = await serverAssets.getItemRaw("image.png");

Note

在开发环境中(以及预渲染期间),服务器资源会直接使用 fs 驱动从文件系统读取,因此 getMeta() 返回的是文件系统统计信息(mtime、size 等),而不是 type 和 etag。在生产环境中,资源会被捆绑到构建输出中,其元数据会在构建时预先计算。

Read more in Docs > Assets.

#运行时配置

在挂载点配置直到运行时才知道的场景中,Nitro 可以使用插件在启动期间动态添加挂载点。

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

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

  // Dynamically pass credentials from runtime config or elsewhere
  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 the driver
  storage.mount("redis", driver)
})

Important

以这种方式挂载的驱动不在 Nitro 的驱动依赖检测范围内:请自行安装第三方库(上例中的 ioredis),并通过驱动的 lib 选项传入。如果不传入,驱动会回退到动态 import("ioredis"),而打包器无法静态解析该导入,导致挂载在运行时失败,并出现 Cannot import ioredis 错误。