☰
lowdb 实战指南:从 CLI、Express 服务器到浏览器与内存模式的官方示例全解析
2026/10/1 2:36:14 网站建设 项目流程
  • 数据库
  • 嵌入式数据库

【免费下载链接】lowdb

Simple and fast JSON database

项目地址:https://gitcode.com/gh_mirrors/lo/lowdb
点击查看免费下载

本篇技术指南围绕 lowdb 官方src/examples/目录下的四个示例展开,逐一拆解 CLI 命令行工具、Express 服务器、浏览器 localStorage 以及测试环境内存模式的具体写法,并下沉到 预设(Presets)、核心类 Low/LowSync 与各类 适配器 的源码实现,帮助你快速掌握在不同运行环境中以最小代码量落地 lowdb 的完整套路。读完本文,你将能独立把 lowdb 嵌入 Node CLI、HTTP 服务、浏览器应用与单元测试四种典型场景。

一、官方示例总览:一条文档带你看遍四种运行环境

lowdb 的官方示例集中放在仓库的 src/examples/ 目录,配套的 src/examples/README.md 以一张清单的形式给出了四个示例的定位,分别对应四种典型使用场景:

示例文件应用场景核心适配器/预设
cli.ts命令行工具(CLI)JSONFileSyncPreset(同步文件预设)
server.tsExpress HTTP 服务器JSONFilePreset(异步文件预设)
browser.ts浏览器页面LocalStoragePreset(localStorage 预设)
in-memory.ts测试中的快速读写MemorySync(内存同步适配器)

这四个示例恰好覆盖了 lowdb 面向 Node、Electron 与浏览器三大运行环境的核心用法,也印证了项目根目录 package.json 中定义的三套子路径导出:

  • lowdb/node—— 文件类适配器与 Node 端预设(JSONFile、JSONFileSync、TextFile、DataFile等);
  • lowdb/browser—— 浏览器端适配器与预设(LocalStorage、SessionStorage等);
  • lowdb(根入口)—— 与运行环境无关的核心类与内存适配器(Low、LowSync、Memory、MemorySync)。

本文接下来的每个小节都会先讲示例本身的用法,再结合 src/presets 与 src/adapters 中的源码解释其底层原理。

二、CLI 示例:用同步预设实现零配置命令行工具

cli.ts是四个示例中最短的一个,完整代码如下(见 src/examples/cli.ts):

import { JSONFileSyncPreset } from '../presets/node.js' type Data = { messages: string[] } const message = process.argv[2] || '' const defaultData: Data = { messages: [] } const db = JSONFileSyncPreset<Data>('file.json', defaultData) db.update(({ messages }) => messages.push(message))

它的行为很简单:从命令行第二个参数(process.argv[2])读取一段文本,追加到file.json的messages数组中。由于命令行工具通常需要"即调即用",所以这里选用同步预设JSONFileSyncPreset,无需await即可完成读写。

2.1 同步预设的源码解析

JSONFileSyncPreset定义在 src/presets/node.ts,其内部逻辑是:

export function JSONFileSyncPreset<Data>( filename: PathLike, defaultData: Data, ): LowSync<Data> { const adapter = process.env.NODE_ENV === 'test' ? new MemorySync<Data>() : new JSONFileSync<Data>(filename) const db = new LowSync<Data>(adapter, defaultData) db.read() return db }

这里有一个值得注意的细节:当环境变量NODE_ENV等于test时,预设会自动改用内存适配器MemorySync,而不是真正读写文件。这正是项目 README 中所宣称的"测试期间自动切换为快速内存模式"(Automatically switches to fast in-memory mode during tests)的实现位置。也就是说,同一个预设代码在测试与生产环境中会表现出不同的持久化行为,这为后续第四节的测试思路埋下了伏笔。

2.2 同步链路与 update 方法

JSONFileSyncPreset返回的是 src/core/Low.ts 中的LowSync实例。示例中调用的db.update(({ messages }) => messages.push(message))对应LowSync.update的实现:

update(fn: (data: T) => unknown): void { fn(this.data) this.write() }

它的语义是"先执行修改函数,再自动落盘",一次调用完成"改数据 + 写文件"两步。如果去掉update,等效写法是:

db.data.messages.push(message) db.write()

从源码可以看到,LowSync.read()只有在适配器返回了有效数据时才覆盖this.data:

read(): void { const data = this.adapter.read() if (data) this.data = data }

因此在 CLI 场景下,file.json首次不存在时,db.data会保持构造时传入的defaultData(即{ messages: [] }),随后update写入第一条消息,file.json即被创建。你可以用node cli.ts "hello"连续执行几次,观察file.json的内容如何累积。

三、服务器示例:Express 异步处理与避免阻塞

src/examples/server.ts 展示了如何在 Express 服务器中使用 lowdb,完整代码为:

import express from 'express' import asyncHandler from 'express-async-handler' import { JSONFilePreset } from '../presets/node.js' const app = express() app.use(express.json()) type Post = { id: string body: string } type Data = { posts: Post[] } const defaultData: Data = { posts: [] } const db = await JSONFilePreset<Data>('db.json', defaultData) // db.data can be destructured to avoid typing `db.data` everywhere const { posts } = db.data app.get('/posts/:id', (req, res) => { const post = posts.find((p) => p.id === req.params.id) res.send(post) }) app.post( '/posts', asyncHandler(async (req, res) => { const post = req.body as Post post.id = String(posts.length + 1) await db.update(({ posts }) => posts.push(post)) res.send(post) }), ) app.listen(3000, () => { console.log('listening on port 3000') })

3.1 为什么服务器要用异步适配器

cli.ts用同步JSONFileSync,而server.ts改用异步JSONFile,注释里明确说明了取舍(见 src/examples/server.ts):

如果你在开发一个本地服务器且不会遇到并发请求,用JSONFileSync会更省事;但如果需要避免阻塞请求,就应该使用JSONFile。

原因在于:同步文件 I/O 会阻塞 Node.js 事件循环,一旦同时到达多个请求,磁盘读写期间的请求都会排队等待;而JSONFile走的是异步适配器接口(read(): Promise<T | null>、write(data): Promise<void>,定义见 src/core/Low.ts),由Low类驱动,不会阻塞事件循环。

3.2 异步预设与"读取即就绪"

JSONFilePreset同样定义在 src/presets/node.ts:

export async function JSONFilePreset<Data>( filename: PathLike, defaultData: Data, ): Promise<Low<Data>> { const adapter = process.env.NODE_ENV === 'test' ? new Memory<Data>() : new JSONFile<Data>(filename) const db = new Low<Data>(adapter, defaultData) await db.read() return db }

注意它返回的是Promise<Low<Data>>,并且在返回前已经执行过await db.read()。所以示例中await JSONFilePreset(...)之后,db.data里就已经装载了db.json的既有内容,随后const { posts } = db.data的解构可以直接使用。

3.3 一个值得留意的更新语义

示例的 POST 路由里:

post.id = String(posts.length + 1) await db.update(({ posts }) => posts.push(post))

由于posts是db.data.posts的解构引用,posts.push(post)实际修改的就是db.data中的数组;update执行完修改函数后自动调用write(),把整个db.data序列化写回db.json。这里体现了 lowdb 的核心设计——db.data就是一个普通 JavaScript 对象/数组,没有任何魔法("no magic"),你用原生数组方法修改它,再通过write()落盘即可。

3.4 并发请求的适用前提

需要明确:lowdb 并不是为高并发写入设计的数据库。仓库根 README.md 的 Limits 一节明确指出,lowdb 不支持 Node 的 cluster 模块;如果确有并发、多进程写入需求,应改用 PostgreSQL、MongoDB 等真正的数据库。因此本示例更适合本地开发、原型搭建或低频写入的场景。

四、浏览器示例:localStorage 预设实现页面持久化

src/examples/browser.ts 是浏览器端最小示例:

import { LocalStoragePreset } from '../presets/browser.js' type Data = { messages: string[] } const defaultData: Data = { messages: [] } const db = LocalStoragePreset<Data>('db', defaultData) db.update(({ messages }) => messages.push('foo'))

这段代码做了三件事:以db作为 localStorage 的存储键名创建预设 → 从localStorage读取数据(不存在则使用默认值)→ 追加一条消息并写回。执行后,开发者工具中localStorage的db键下会存有{"messages":["foo"]}。

4.1 浏览器预设源码

LocalStoragePreset定义在 src/presets/browser.ts:

export function LocalStoragePreset<Data>( key: string, defaultData: Data, ): LowSync<Data> { const adapter = new LocalStorage<Data>(key) const db = new LowSync<Data>(adapter, defaultData) db.read() return db }

同目录下还提供了等价的SessionStoragePreset(见 src/presets/browser.ts),差别仅在底层使用sessionStorage。二者都基于LowSync——因为 Web Storage API 本身是同步的。

4.2 LocalStorage 适配器实现

适配器实现在 src/adapters/browser/LocalStorage.ts,其核心契约满足 src/core/Low.ts 中SyncAdapter的接口:read()返回数据或null,write(data)写回存储。此外浏览器目录下还有SessionStorage适配器(见 src/adapters/browser/SessionStorage.ts),用于 session 级的临时持久化。

使用前提:LocalStorage/SessionStorage只能运行在浏览器环境(存在window全局对象),且存储值必须是可被JSON.stringify序列化的数据。若要在 Node 中模拟,则需配合内存适配器或文件适配器。

五、内存示例:在测试中替代文件 I/O

src/examples/in-memory.ts 演示了如何用内存适配器让测试读写"零 I/O":

import { LowSync, MemorySync, SyncAdapter } from '../index.js' import { JSONFileSync } from '../node.js' declare global { namespace NodeJS { interface ProcessEnv { NODE_ENV: 'test' | 'dev' | 'prod' } } } type Data = Record<string, unknown> const defaultData: Data = {} const adapter: SyncAdapter<Data> = process.env.NODE_ENV === 'test' ? new MemorySync<Data>() : new JSONFileSync<Data>('db.json') const db = new LowSync<Data>(adapter, defaultData) db.read() // Rest of your code...

它的思路非常直接:根据NODE_ENV环境变量选择适配器——测试环境用MemorySync,其他环境用JSONFileSync。由于MemorySync的write()只是把数据存进内存变量(见 src/adapters/Memory.ts),示例开头的注释明确写道:"With this adapter, callingdb.write()will do nothing"(调用write()不会真正落盘),因此测试运行既快又不会污染磁盘上的db.json。

5.1 与预设内置的"测试自动切换"呼应

这一写法与第二节提到的JSONFilePreset/JSONFileSyncPreset内置逻辑殊途同归——预设已经在内部实现了NODE_ENV === 'test'时自动切换为Memory/MemorySync。两者的关系是:

  • 用预设:框架替你做了适配器切换,代码更简洁;
  • 手动选择适配器(本例):把切换逻辑显式暴露出来,便于在测试中注入其他定制适配器或 Mock 数据。

5.2 内存适配器源码

MemorySync与异步版本Memory均定义在 src/adapters/Memory.ts:

export class MemorySync<T> implements SyncAdapter<T> { #data: T | null = null read(): T | null { return this.#data || null } write(obj: T): void { this.#data = obj } }

可以看到write只是把对象引用保存到私有字段#data,不涉及任何磁盘、网络开销;read在无数据时返回null。这也解释了为什么它被称为"测试专用加速器"——省去了JSON.stringify序列化与磁盘写入的时间。仓库中 src/adapters/Memory.test.ts 即对该行为进行了单元测试验证。

六、串起来看:预设、类与适配器的协作关系

四个示例虽然面向不同环境,但共享同一套由"预设 → 类 → 适配器"组成的三层架构。以异步链为例,调用链为:

JSONFilePreset └─ new JSONFile<Data>(filename) // 适配器:文件读写 + JSON 序列化 └─ extends DataFile // 组合 TextFile,注入 parse/stringify └─ new Low<Data>(adapter, defaultData) └─ await db.read() // 从适配器读取,填充 db.data └─ db.update(fn) / db.write() // 修改数据并写回

其中:

  • 预设层(src/presets/node.ts、src/presets/browser.ts)提供四种常用组合:JSONFilePreset、JSONFileSyncPreset、LocalStoragePreset、SessionStoragePreset,内部完成"选适配器 + 建 Low 实例 + 读取数据"三步;
  • 核心类层(src/core/Low.ts)提供Low(异步)与LowSync(同步)两套实现,方法只有read()、write()、update(fn)三个,data属性直接存放业务数据;
  • 适配器层(src/adapters)按环境分为node/(JSONFile、JSONFileSync、TextFile、DataFile)、browser/(LocalStorage、SessionStorage)与通用的Memory/MemorySync。

6.1 文件适配器的序列化细节

值得补充的是JSONFile的默认格式化行为。查看 src/adapters/node/JSONFile.ts 可以看到它继承自DataFile,并注入了序列化参数:

export class JSONFile<T> extends DataFile<T> { constructor(filename: PathLike) { super(filename, { parse: JSON.parse, stringify: (data: T) => JSON.stringify(data, null, 2), }) } }

其中stringify使用JSON.stringify(data, null, 2)以两空格缩进写出 JSON,保证db.json文件可读;而 src/adapters/node/DataFile.ts 则把"读取文本 → parse"与"stringify → 写入文本"两段逻辑解耦,这为换用 YAML、加密、压缩等其他格式提供了扩展点(项目 README 的 Adapters 章节 对此有专门介绍)。

6.2 快速跳转清单

如果你想把本文的示例直接改造成自己的项目,可参考以下路径:

  • 四个示例的完整源码:src/examples/cli.ts、src/examples/server.ts、src/examples/browser.ts、src/examples/in-memory.ts;
  • 预设实现:src/presets/node.ts、src/presets/browser.ts;
  • 核心类:src/core/Low.ts;
  • 各适配器:src/adapters/node、src/adapters/browser、src/adapters/Memory.ts;
  • 测试参考:src/core/Low.test.ts、src/adapters/Memory.test.ts、src/adapters/node/JSONFile.test.ts。

七、边界与注意事项

在使用以上示例时,有几点需要结合 lowdb 的设计边界加以留意(依据来自仓库根 README.md 的 Limits 一节):

  1. 不支持 Node 的 cluster 模块:多进程共享同一db.json无法保证写入一致性,分布式部署场景请改用真正的数据库。
  2. 全量序列化开销:每次db.write()都会对整个db.data执行JSON.stringify并整体写入。当数据量达到约 10~100MB 级别时可能遇到性能问题。官方建议的做法是批量操作、按需落盘(尽量集中修改后再调用一次write()),而不是每次小改动都写一次。
  3. 运行环境要求:本项目为纯 ESM 包,package.json中"type": "module",并声明engines.node >= 18。浏览器适配器仅在存在window的环境中可用;在 Node 中使用时请从lowdb/node子路径导入,在浏览器中使用时从lowdb/browser子路径导入。

综上,src/examples/的四个示例几乎就是 lowdb 最小可用用法的"标准答案":CLI 用同步预设、服务器用异步预设、浏览器用 Web Storage 预设、测试用内存适配器。理解了这一套对应关系,你就能在任何 JavaScript/TypeScript 环境中快速落地一个"读即建、改即存"的本地 JSON 数据库。

  • 数据库
  • 嵌入式数据库

【免费下载链接】lowdb

Simple and fast JSON database

项目地址:https://gitcode.com/gh_mirrors/lo/lowdb
点击查看免费下载
上一篇:如何高效获取微信公众号数据:开源爬虫工具的完整实践指南
下一篇:Apate文件伪装工具:快速解决文件格式限制的实用方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询