Applications and operations
Use SQLite from Node.js
Open SQLite from Node.js, configure lock waits, bind values, read rows, verify foreign keys, and close the connection.
Node.js now ships a SQLite driver in the standard library, so a script can use a real database with zero dependencies.
node:sqlite was added in Node.js 22.5. As of July 2026, Node marks it as a release-candidate API with stability 1.2. The example below needs a current supported Node release with the timeout option.
Create app.js:
import { DatabaseSync } from 'node:sqlite'
const db = new DatabaseSync('notes.db', { timeout: 3000 })
db.exec('PRAGMA foreign_keys = ON')
const foreignKeys = db.prepare('PRAGMA foreign_keys').get()
console.log(foreignKeys)
db.exec(`
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL
) STRICT
`)
const insert = db.prepare('INSERT INTO notes (title) VALUES (?)')
insert.run('Plan the week')
const notes = db.prepare(
'SELECT id, title FROM notes ORDER BY id'
).all()
console.log(notes)
db.close()
Run it with node app.js:
[Object: null prototype] { foreign_keys: 1 }
[ [Object: null prototype] { id: 1, title: 'Plan the week' } ]
The foreign_keys: 1 line proves enforcement is on for this connection. Current node:sqlite versions enable foreign keys by default, but setting and reading the pragma makes the application requirement explicit.
The pieces worth noticing
The timeout lets a short lock conflict wait for three seconds. Without it, a concurrent writer makes your statement fail immediately with a locked-database error instead of waiting its turn. Three seconds absorbs normal contention; if you still hit lock errors, some connection is holding a transaction open too long.
db.exec() runs statements without results — pragmas, DDL. db.prepare() compiles a statement you can run many times with different values. The ? placeholder is how values get in: insert.run('Plan the week') binds the string safely, with no string concatenation and no SQL injection surface.
Prepared statements give you three read shapes: .get() returns the first row, .all() returns every row as an array, and .run() executes a write and reports { changes, lastInsertRowid }.
Call db.close() when the work is done. It releases the file locks and flushes state; a script that never closes can leave a WAL file waiting for a checkpoint.
Where the synchronous model fits
DatabaseSync runs every operation on the JavaScript thread. There are no promises to await, which is pleasant — but a query that takes 200 ms blocks your entire process for 200 ms.
It fits scripts, tests, command-line tools, and light application work. Long queries or heavy concurrent traffic block that thread, so choose another architecture when the workload needs it: worker threads, or a server database.
Lesson completed