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

Take this course offline

Get every free book, course edition, and software download.

Get the download library →