Tiny local JSON database for Node, Electron and the browser
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
File-based storage adapters and convenience presets for Node.js environments. These adapters support JSON files, plain text files, and custom data formats with atomic write operations for data safety.
Pre-configured database instances with JSON file adapters for immediate use.
/**
* Create a Low instance with JSONFile adapter, automatically switches to Memory adapter during tests
* @param filename - Path to the JSON file
* @param defaultData - Default data structure
* @returns Promise resolving to configured Low instance with data pre-loaded
*/
function JSONFilePreset<Data>(filename: PathLike, defaultData: Data): Promise<Low<Data>>;
/**
* Create a LowSync instance with JSONFileSync adapter, automatically switches to MemorySync adapter during tests
* @param filename - Path to the JSON file
* @param defaultData - Default data structure
* @returns Configured LowSync instance with data pre-loaded
*/
function JSONFileSyncPreset<Data>(filename: PathLike, defaultData: Data): LowSync<Data>;Usage Examples:
import { JSONFilePreset, JSONFileSyncPreset } from "lowdb/node";
// Async preset
type Data = { posts: Array<{ id: number; title: string }> };
const defaultData: Data = { posts: [] };
const db = await JSONFilePreset("db.json", defaultData);
await db.update(({ posts }) => posts.push({ id: 1, title: "Hello World" }));
// Sync preset
const syncDb = JSONFileSyncPreset("sync-db.json", { settings: {} });
syncDb.update((data) => {
data.settings.theme = "dark";
});Direct JSON file adapters with automatic parsing and formatting.
/**
* Async JSON file adapter with automatic JSON parsing/stringifying
* @template T - The type of data stored in the JSON file
*/
class JSONFile<T> implements Adapter<T> {
constructor(filename: PathLike);
/** Read and parse JSON file content, returns null if file doesn't exist */
read(): Promise<T | null>;
/** Stringify and write data to JSON file with atomic write operation */
write(obj: T): Promise<void>;
}
/**
* Sync JSON file adapter with automatic JSON parsing/stringifying
* @template T - The type of data stored in the JSON file
*/
class JSONFileSync<T> implements SyncAdapter<T> {
constructor(filename: PathLike);
/** Read and parse JSON file content synchronously, returns null if file doesn't exist */
read(): T | null;
/** Stringify and write data to JSON file synchronously with atomic write operation */
write(obj: T): void;
}Usage Examples:
import { Low, LowSync } from "lowdb";
import { JSONFile, JSONFileSync } from "lowdb/node";
// Async JSON file adapter
const adapter = new JSONFile("data.json");
const db = new Low(adapter, { users: [] });
await db.read();
db.data.users.push({ id: 1, name: "Alice" });
await db.write();
// Sync JSON file adapter
const syncAdapter = new JSONFileSync("config.json");
const syncDb = new LowSync(syncAdapter, { theme: "light" });
syncDb.read();
syncDb.data.theme = "dark";
syncDb.write();Raw text file adapters for custom data formats and plain text storage.
/**
* Async text file adapter using steno for atomic writes
*/
class TextFile implements Adapter<string> {
constructor(filename: PathLike);
/** Read text file content, returns null if file doesn't exist */
read(): Promise<string | null>;
/** Write text to file with atomic write operation using steno */
write(str: string): Promise<void>;
}
/**
* Sync text file adapter with atomic writes using temporary files
*/
class TextFileSync implements SyncAdapter<string> {
constructor(filename: PathLike);
/** Read text file content synchronously, returns null if file doesn't exist */
read(): string | null;
/** Write text to file synchronously with atomic write using temporary file */
write(str: string): void;
}Usage Examples:
import { Low, LowSync } from "lowdb";
import { TextFile, TextFileSync } from "lowdb/node";
// Async text file adapter
const textAdapter = new TextFile("log.txt");
const logDb = new Low(textAdapter, "");
await logDb.read();
await logDb.update((data) => data + "New log entry\n");
// Sync text file adapter
const syncTextAdapter = new TextFileSync("notes.txt");
const notesDb = new LowSync(syncTextAdapter, "");
notesDb.read();
notesDb.data += "Important note\n";
notesDb.write();Flexible adapters for custom data formats with configurable parse and stringify functions.
/**
* Async data file adapter with custom parse/stringify functions
* @template T - The type of data stored
*/
class DataFile<T> implements Adapter<T> {
constructor(
filename: PathLike,
options: {
parse: (str: string) => T;
stringify: (data: T) => string;
}
);
/** Read file content and parse using custom parse function */
read(): Promise<T | null>;
/** Stringify data using custom stringify function and write to file */
write(obj: T): Promise<void>;
}
/**
* Sync data file adapter with custom parse/stringify functions
* @template T - The type of data stored
*/
class DataFileSync<T> implements SyncAdapter<T> {
constructor(
filename: PathLike,
options: {
parse: (str: string) => T;
stringify: (data: T) => string;
}
);
/** Read file content and parse using custom parse function synchronously */
read(): T | null;
/** Stringify data using custom stringify function and write to file synchronously */
write(obj: T): void;
}Usage Examples:
import { Low } from "lowdb";
import { DataFile } from "lowdb/node";
import YAML from "yaml";
// YAML file adapter
const yamlAdapter = new DataFile("config.yaml", {
parse: YAML.parse,
stringify: YAML.stringify
});
const yamlDb = new Low(yamlAdapter, { servers: [] });
await yamlDb.read();
// CSV file adapter with custom parsing
const csvAdapter = new DataFile("data.csv", {
parse: (str) => str.split('\n').map(line => line.split(',')),
stringify: (data) => data.map(row => row.join(',')).join('\n')
});
// Encrypted JSON adapter
const encryptedAdapter = new DataFile("secret.json", {
parse: (str) => JSON.parse(decrypt(str)),
stringify: (data) => encrypt(JSON.stringify(data))
});All file adapters use atomic write operations to prevent data corruption:
steno library for atomic writes with temporary filesfs.renameSync for atomic operationsWhen a file doesn't exist:
const db = await JSONFilePreset("nonexistent.json", { count: 0 });
// db.data will be { count: 0 } (default data)
await db.read(); // No error thrown, uses default data
// db.data remains { count: 0 }
await db.write(); // Creates the file with current dataFile adapters do not automatically create parent directories. Ensure directories exist:
import { mkdir } from "fs/promises";
import { JSONFilePreset } from "lowdb/node";
// Create directory if needed
await mkdir("data", { recursive: true });
const db = await JSONFilePreset("data/app.json", {});Presets automatically switch to memory adapters when NODE_ENV=test:
// In test environment (NODE_ENV=test)
const db = await JSONFilePreset("db.json", { posts: [] });
// Uses Memory adapter instead of JSONFile
// No actual file I/O occurs during testing
// In production environment
const db = await JSONFilePreset("db.json", { posts: [] });
// Uses JSONFile adapter for actual file persistenceFile adapters may throw various Node.js file system errors:
import { JSONFilePreset } from "lowdb/node";
try {
const db = await JSONFilePreset("/readonly/db.json", {});
await db.write();
} catch (error) {
if (error.code === "EACCES") {
console.error("Permission denied");
} else if (error.code === "ENOENT") {
console.error("Directory doesn't exist");
} else if (error.code === "ENOSPC") {
console.error("No space left on device");
}
}
// Handle JSON parsing errors
try {
const db = await JSONFilePreset("malformed.json", {});
await db.read();
} catch (error) {
if (error instanceof SyntaxError) {
console.error("Invalid JSON format");
}
}lowdb loads the entire file into memory. For large datasets (>100MB), consider:
Atomic writes create temporary files. For high-frequency writes:
update() method/** Node.js path-like type (string, Buffer, or URL) */
type PathLike = string | Buffer | URL;