node:sqlite — SQLite nativo no Node.js

Desde o Node.js 22.5, o runtime traz um módulo SQLite embutido e síncrono: veja como criar bancos em memória ou em arquivo, preparar statements e consultar dados sem instalar nenhuma dependência nativa.

Toda aplicação Node.js que precisa de um banco local — cache, fila de jobs, dados de teste, protótipo rápido — acabava instalando better-sqlite3 ou sqlite3 como dependência nativa. Desde o Node.js 22.5.0, o runtime traz seu próprio módulo node:sqlite: um binding síncrono ao SQLite embutido no binário do Node, sem npm install e sem compilação nativa.

Criando e consultando um banco

A classe principal é DatabaseSync. O construtor aceita um caminho de arquivo ou a string especial :memory: para um banco em memória, que desaparece quando o processo termina:

const { DatabaseSync } = require('node:sqlite');

const db = new DatabaseSync(':memory:');

db.exec(`
    CREATE TABLE produtos(
        id INTEGER PRIMARY KEY,
        nome TEXT,
        preco REAL
    ) STRICT
`);

db.exec("INSERT INTO produtos (nome, preco) VALUES ('Teclado', 150.0)");
db.exec("INSERT INTO produtos (nome, preco) VALUES ('Mouse', 50.0)");

const linhas = db.prepare('SELECT * FROM produtos ORDER BY preco').all();
console.log(linhas);
// [ { id: 2, nome: 'Mouse', preco: 50 }, { id: 1, nome: 'Teclado', preco: 150 } ]

exec() roda SQL sem parâmetros e sem retorno — ideal para criar tabelas. Para consultas reais, o caminho é prepare(), que compila o SQL numa StatementSync reutilizável.

Statements preparados e parâmetros

Uma StatementSync aceita parâmetros posicionais (?) ou nomeados (:nome, $nome, @nome) e expõe três métodos principais: run() para escritas, get() para a primeira linha e all() para todas as linhas:

const inserir = db.prepare('INSERT INTO produtos (nome, preco) VALUES (?, ?)');

const resultado = inserir.run('Monitor', 899.9);
console.log(resultado);
// { changes: 1, lastInsertRowid: 3 }

const buscarPorNome = db.prepare('SELECT * FROM produtos WHERE nome = :nome');
console.log(buscarPorNome.get({ nome: 'Monitor' }));
// { id: 3, nome: 'Monitor', preco: 899.9 }

Como todas as chamadas são síncronas, não existe await nem callback — o que simplifica scripts e testes, mas significa que consultas pesadas bloqueiam o event loop, exatamente como fs.readFileSync().

Iterando resultados grandes

Para não carregar uma tabela inteira na memória de uma vez, iterate() devolve um iterador que produz uma linha por vez e funciona direto num for...of:

const todos = db.prepare('SELECT * FROM produtos ORDER BY id');

for (const produto of todos.iterate()) {
    console.log(`${produto.nome}: R$ ${produto.preco}`);
}

Banco em arquivo e opções de abertura

Trocar :memory: por um caminho de arquivo persiste os dados em disco. O construtor aceita um segundo argumento com opções, como abrir em modo somente leitura:

const somenteLeitura = new DatabaseSync('./dados/produtos.db', {
    readOnly: true,
});

const relatorio = somenteLeitura.prepare('SELECT COUNT(*) AS total FROM produtos').get();
console.log(relatorio.total);

somenteLeitura.close();

close() fecha a conexão e libera o arquivo; statements abertos também têm seu próprio close() para liberar recursos antes de fechar o banco.

Casos de uso

Cuidados

Por ser síncrono, node:sqlite não é a escolha certa para servidores web com alto volume de escrita concorrente — bibliotecas como better-sqlite3 existem há mais tempo, têm mais recursos (backup incremental, extensões) e também são síncronas, mas por escolha de design, não por imaturidade da API nativa. A classe DatabaseSync ainda é marcada como experimental mesmo depois de não exigir mais flag, então sua API pode mudar entre versões menores do Node — vale checar o changelog antes de atualizar em produção.

Compatibilidade

O módulo node:sqlite chegou no Node.js 22.5.0, atrás da flag --experimental-sqlite. A flag deixou de ser necessária no Node.js 23.4.0 e no 22.13.0 (LTS), embora o módulo continue classificado como experimental nessas versões. Use node --version para confirmar: em qualquer Node.js 22.13.0 ou mais recente, require('node:sqlite') já funciona sem flags.

documentação oficial

Conclusão

Com node:sqlite, um banco relacional de verdade passou a fazer parte do que o Node.js oferece de fábrica, do mesmo jeito que aconteceu com o test runner e o carregamento de .env. Para scripts, CLIs, testes e protótipos, isso elimina mais uma dependência nativa da lista — e para cargas mais pesadas, continua valendo a pena olhar para bibliotecas estabelecidas como better-sqlite3.

Tecnologia:
node

Deixe seu Comentário!

Dúvida, sugestão ou só um "valeu!" — tudo é bem-vindo por aqui.

Comentário
Nome
Email
lock Seu e-mail não será publicado.