Node.js trae SQLite integrado: el módulo node:sqlite, que en Node 24 ya no necesita ninguna opción para usarse. Para SQLite desde Node.js ya no hace falta instalar un paquete con partes compiladas. Este ejemplo usa la base «tienda» guardada en tienda.db.
También te puede interesar
Los programas se ejecutaron en un contenedor limpio; Node 24.22 incluye una versión de SQLite más nueva que la de Python 3.13:
versionesNode v24.22.0 · SQLite 3.53.4
En la documentación de Node 24, el módulo tiene la estabilidad 1.2, «Release candidate»: la API ya no debería cambiar, pero todavía no es definitiva. En Node 24.22 no muestra ningún aviso al cargarlo.
Abrir la base
Una función pequeña que abre tienda.db y, si está vacía, carga la base de ejemplo. DatabaseSync es síncrona: cada llamada espera a que SQLite termine, lo normal con una base local.
// Abre (o crea) tienda.db y, si está vacía, carga la base de ejemplo
import { DatabaseSync } from 'node:sqlite';
import { readFileSync } from 'node:fs';
export function abrir() {
const db = new DatabaseSync('/tmp/tienda.db');
const hay = db.prepare("SELECT COUNT(*) AS n FROM sqlite_master WHERE name = 'productos'").get().n;
if (!hay) db.exec(readFileSync('/datos/tienda.sql', 'utf8'));
return db;
}Consultas preparadas
prepare compila la consulta una vez; all() devuelve todas las filas como objetos y get(), la primera. Los parámetros van por posición (?) o por nombre (:pedido, con un objeto):
import { abrir } from './crear.mjs';
const db = abrir();
const porPais = db.prepare(`
SELECT c.nombre, COUNT(p.id) AS pedidos
FROM clientes c LEFT JOIN pedidos p ON p.cliente_id = c.id
WHERE c.pais = ?
GROUP BY c.id, c.nombre
ORDER BY pedidos DESC, c.nombre`);
for (const fila of porPais.all('España')) {
console.log(`${fila.nombre.padEnd(16)} ${fila.pedidos} ${fila.pedidos === 1 ? 'pedido' : 'pedidos'}`);
}
// Parámetros con nombre y una sola fila con get()
const { total } = db
.prepare('SELECT SUM(cantidad * precio_unitario) AS total FROM detalle WHERE pedido_id = :pedido')
.get({ pedido: 8 });
const usd = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });
console.log(`Pedido 8: ${usd.format(total)} USD`);
db.close();node consultar.mjs
salidaLuis Fernández 3 pedidos Lucía Gómez 2 pedidos Paula Morales 1 pedido Pedido 8: $3,638.78 USD
Insertar
run() ejecuta la sentencia y devuelve cuántas filas cambió y el último id insertado:
import { abrir } from './crear.mjs';
const db = abrir();
const insertar = db.prepare(
'INSERT INTO productos (id, nombre, categoria_id, precio, stock) VALUES (?, ?, ?, ?, ?)');
const r = insertar.run(16, 'Hub USB-C', 3, 34.9, 12);
console.log(r); // filas cambiadas y último id
console.log(db.prepare('SELECT nombre, precio FROM productos WHERE id = 16').get());
db.close();node insertar.mjs
salida{ changes: 1, lastInsertRowid: 16 }
[Object: null prototype] { nombre: 'Hub USB-C', precio: 34.9 }Transacciones
No hay un método especial: se ejecutan BEGIN, COMMIT y ROLLBACK con exec. Si el segundo producto falla, el primero se deshace también:
import { abrir } from './crear.mjs';
const db = abrir();
const insertar = db.prepare(
'INSERT INTO productos (id, nombre, categoria_id, precio, stock) VALUES (?, ?, ?, ?, ?)');
const contar = () => db.prepare('SELECT COUNT(*) AS n FROM productos').get().n;
console.log('antes:', contar());
db.exec('BEGIN');
try {
insertar.run(17, 'Soporte de monitor', 2, 45, 10);
insertar.run(18, 'Producto de prueba', 3, 0, 1); // precio 0: no pasa el CHECK
db.exec('COMMIT');
} catch (e) {
db.exec('ROLLBACK');
console.log('Error:', e.message);
}
console.log('después:', contar());
db.close();node transaccion.mjs
salidaantes: 16 Error: CHECK constraint failed: precio > 0 después: 16
Qué tipos llegan a JavaScript
Los números de SQLite llegan como number y el texto como string, fechas incluidas. Un entero mayor que 253 no cabe en un number sin perder precisión, y node:sqlite prefiere dar un error a devolverlo mal:
import { abrir } from './crear.mjs';
const db = abrir();
// Lo que llega a JavaScript: números y texto (las fechas de SQLite son texto)
const p = db.prepare('SELECT nombre, precio, stock FROM productos WHERE id = 2').get();
const f = db.prepare('SELECT fecha FROM pedidos WHERE id = 1').get();
console.log(p.precio, typeof p.precio, '|', f.fecha, typeof f.fecha);
// Un entero mayor que 2^53 no cabe en un number: sin más, error
const grande = db.prepare('SELECT 9007199254740993 AS n');
try {
console.log(grande.get().n);
} catch (e) {
console.log('Error:', e.message);
}
// Con setReadBigInts(true), los enteros llegan como BigInt
grande.setReadBigInts(true);
console.log(grande.get().n);
db.close();node tipos.mjs
salida1849 number | 2026-01-10 string Error: Value is too large to be represented as a JavaScript number: 9007199254740993 9007199254740993n
node:sqlite, lo habitual era better-sqlite3 (versión 13.0.3 en npm al escribir esto), con una API muy parecida y también síncrona. Sigue siendo una buena opción si necesitas algo que el módulo integrado aún no tenga; para empezar, el integrado basta y no añade dependencias.Todo el código de esta página se ejecutó con Node.js 24.22 y su SQLite 3.53.4 en un contenedor limpio antes de publicarla; las salidas están copiadas de esa ejecución. Documentación oficial: módulo node:sqlite de Node.js 24.