node:sqlite
node:sqlite gives Node.js a built-in SQLite interface. It lets you query a local database file or an in-memory database without adding an npm dependency. SQLite runs inside your process; there is no separate database server.
The examples target the DatabaseSync and StatementSync API in Node.js 22.x from 22.13.0 onward, and Node.js 24.x. The module was added in 22.5.0 behind --experimental-sqlite; the flag requirement was removed in 22.13.0 and 23.4.0. These examples need no flag.
As of October 2026, the API is not stable: 22.x documentation marks it Stability 1.1 โ Active development, while newer 24.x releases and the current reference mark it Stability 1.2 โ Release candidate. The unversioned reference now documents Database and Statement instead. Use the 22.13.0 baseline or the 24.x reference for these examples, and check your runtime's versioned documentation before upgrading.
Use casesโ
- CLI tools that store local state, such as a task list or import history
- Build scripts that query structured data without provisioning a database service
- Local caches that need indexes and SQL queries
- Tests that need a disposable relational database in memory
- Small, single-host applications with limited write concurrency
Opening the database, executing SQL, and reading rows through this API are synchronous. Long queries and lock waits block other work on the same thread. Wrapping a query in a Promise does not make it asynchronous. Keep work bounded, or move database operations to a worker thread when the main thread must stay responsive.
Example โ prepared statements and cleanupโ
Save this as sqlite-example.mjs and run it with node sqlite-example.mjs on one of the versions above.
import { DatabaseSync } from 'node:sqlite';
const db = new DatabaseSync(':memory:');
try {
// exec() is for trusted SQL, not interpolated user input.
db.exec(`
CREATE TABLE IF NOT EXISTS people (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
)
`);
const insert = db.prepare('INSERT INTO people (id, name) VALUES (?, ?)');
console.log(insert.run(1, 'Ada')); // { changes: 1, lastInsertRowid: 1 }
insert.run(2, 'Grace'); // Reuse the prepared statement.
const find = db.prepare('SELECT id, name FROM people WHERE id = :id');
console.log(find.get({ ':id': 1 })); // Row with id 1 and name 'Ada'.
console.log(find.get({ ':id': 999 })); // undefined
const list = db.prepare('SELECT id, name FROM people ORDER BY id');
console.log(list.all()); // Array containing both rows.
} finally {
db.close();
}
':memory:' creates a database that disappears when the connection closes. Replace it with './people.sqlite' to create or open a persistent file relative to the working directory. The file retains rows between runs, so rerunning these inserts against it will fail on the duplicate IDs.
db.exec()executes SQL without parameter binding. Use it for trusted schema definitions and transaction commands.db.prepare()creates a reusable statement. Bind positional?placeholders with arguments in the same order.statement.run()returns{ changes, lastInsertRowid }, not result rows.statement.get()returns the first matching row, orundefinedwhen nothing matches.statement.all()returns an array of rows, or an empty array when nothing matches.- Named placeholders take an object. Explicit keys such as
{ ':id': 1 }avoid relying on version-specific bare-name defaults.
Always close the database in finally, including when SQL throws. This baseline uses db.close(); it does not require a separate statement-close call.
Example โ transactions and rollbackโ
A transaction makes a batch atomic: either all changes commit or they roll back. Use explicit SQL transaction commands; this API does not provide a db.transaction() wrapper.
Insert this continuation inside the previous example's try block, after its queries and before finally. It reuses db and the prepared insert statement. The duplicate ID intentionally triggers a failure.
db.exec('BEGIN');
try {
insert.run(3, 'Linus');
insert.run(1, 'Duplicate ID'); // Fails: id 1 already exists.
db.exec('COMMIT');
} catch (error) {
db.exec('ROLLBACK');
console.error('Batch rolled back:', error.message);
}
console.log(
db.prepare('SELECT id FROM people WHERE id = ?').get(3),
); // undefined: the first insert was rolled back too.
In production, preserve the original failure and handle rollback failures separately so they do not mask it. Some SQLite errors automatically end the transaction, leaving no active transaction for ROLLBACK.
Changing the second ID to 4 allows both inserts to commit. The example catches the failure to show the rollback result; application code should propagate or handle the error according to its caller's needs. Keep transactions short and avoid waiting for network work while holding a write transaction.
Gotchasโ
Bind values, whitelist identifiersโ
Binding keeps a value separate from SQL syntax. For example, a name containing a quote remains data when passed to insert.run(). Do not interpolate user values into SQL strings.
Placeholders cannot stand in for table names, column names, or keywords. For a selectable sort column, map the requested name to a fixed whitelist. This snippet also belongs inside the main example's try block:
const allowedColumns = new Map([['id', 'id'], ['name', 'name']]);
const requestedColumn = 'name'; // Could come from a validated request.
const column = allowedColumns.get(requestedColumn);
if (!column) throw new Error('Unsupported sort column');
const sorted = db.prepare(`SELECT id, name FROM people ORDER BY ${column}`);
console.log(sorted.all());
Only the trusted, whitelisted identifier is interpolated. Bind any filter values separately.
Handle large integers deliberatelyโ
SQLite INTEGER values are signed 64-bit integers. Reads return JavaScript numbers by default, but integers outside JavaScript's safe integer range throw rather than silently lose precision. Enable statement.setReadBigInts(true) when those values are possible:
// Inside the main example's try block:
const largeInteger = db.prepare('SELECT :value AS value');
largeInteger.setReadBigInts(true);
console.log(largeInteger.get({ ':value': 9007199254740993n }));
// Row with value: 9007199254740993n.
This setting makes returned integer columns BigInts and also affects the integer metadata returned by run(). BigInt inputs are supported, but must fit the signed 64-bit range: -9223372036854775808n through 9223372036854775807n. BigInts are not directly JSON-serializable; choose an explicit representation, such as a decimal string, at API boundaries.
Defaults and concurrency matterโ
- Foreign-key enforcement is enabled by default for
DatabaseSync. Invalid references fail when your schema declares foreign keys. - Extension loading is disabled by default. Leave it disabled unless you have a specific need for trusted native extensions.
statement.iterate(), added in 22.13.0, reads rows through a synchronous iterator. It avoids building the completeall()array, but does not make query execution asynchronous.- SQLite permits multiple readers but only one writer at a time. Competing connections can encounter lock contention and
SQLITE_BUSY; plan how to handle it. - A local database file is not a shared database service. Prefer a client-server database for multi-host deployments or sustained concurrent writes. A worker thread can protect event-loop responsiveness, but does not remove SQLite's write-lock constraints.
Alternativesโ
Choose based on deployment and concurrency needs, not an assumed speed advantage.
| Option | Execution model | When it fits |
|---|---|---|
node:sqlite | Built-in, synchronous | Local SQL with no npm database dependency, when the runtime version and API maturity are acceptable |
better-sqlite3 | Dependency, synchronous | Projects that want its established API and transaction helpers |
sqlite3 | Dependency, callback-based asynchronous | Projects that need an asynchronous SQLite interface |
| PostgreSQL | Client-server database, accessed through a client driver | Shared data across hosts, sustained concurrent writes, and centralized database operations |
Further readingโ
- Current Node.js SQLite reference โ latest API names, maturity, and changes
- Node.js 22.13.0 SQLite reference โ versioned baseline for these examples
- Node.js 24.x SQLite reference โ API details for the 24.x branch
- Node.js learning resources โ event loop, tooling, and production guidance