PGlite: PostgreSQL rodando diretamente no browser e em Node.js
1. O que é o PGlite e por que ele existe?
Historicamente, desenvolvedores web que precisavam de um banco de dados relacional no frontend recorriam ao IndexedDB (API nativa do browser) ou ao SQLite via WebAssembly (como o SQL.js). O PostgreSQL, apesar de ser um dos bancos mais poderosos e amplamente adotados no backend, nunca havia sido uma opção viável no navegador — até agora.
O PGlite é uma implementação do PostgreSQL compilada para WebAssembly (WASM) com bindings JavaScript leves. Diferente de soluções que apenas emulam SQL, o PGlite executa o motor real do PostgreSQL dentro do ambiente do browser ou em runtime Node.js, oferecendo suporte a sintaxe completa, tipos de dados, funções agregadas e transações ACID.
Casos de uso principais incluem:
- Aplicações offline-first que precisam de consultas SQL complexas
- Prototipação rápida sem necessidade de configurar servidores de banco de dados
- Testes unitários e de integração que rodam em memória, eliminando dependências externas
- Ferramentas educacionais e ambientes de aprendizado interativos
2. Instalação e primeiros passos
Setup no Node.js
npm install @electric-sql/pglite
import { PGlite } from '@electric-sql/pglite'
const db = new PGlite() // instância em memória
await db.exec(`
CREATE TABLE usuarios (
id SERIAL PRIMARY KEY,
nome TEXT NOT NULL,
email TEXT UNIQUE,
criado_em TIMESTAMP DEFAULT NOW()
);
`)
await db.exec(`
INSERT INTO usuarios (nome, email) VALUES ('Ana Silva', 'ana@exemplo.com');
`)
const resultado = await db.query('SELECT * FROM usuarios;')
console.log(resultado.rows)
// [{ id: 1, nome: 'Ana Silva', email: 'ana@exemplo.com', criado_em: ... }]
Setup no browser (via CDN)
<script type="module">
import { PGlite } from 'https://cdn.jsdelivr.net/npm/@electric-sql/pglite/dist/index.js';
const db = new PGlite({
dataDir: 'idb://meu-banco' // persistência opcional via IndexedDB
});
await db.exec(`
CREATE TABLE tarefas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
descricao TEXT,
concluida BOOLEAN DEFAULT FALSE
);
`);
await db.exec(`INSERT INTO tarefas (descricao) VALUES ('Estudar PGlite');`);
</script>
3. Diferenças fundamentais entre PGlite e PostgreSQL tradicional
O PGlite mantém compatibilidade com a maior parte da sintaxe SQL do PostgreSQL, mas possui limitações importantes:
- Extensões C nativas: extensões como PostGIS, pgvector e TimescaleDB não funcionam, pois exigem binários compilados para arquitetura nativa. Apenas extensões puramente SQL ou WASM são suportadas.
- Performance e concorrência: o PGlite opera em single-thread, diferentemente do PostgreSQL tradicional que usa múltiplos processos. Isso impacta workloads pesados com muitas conexões simultâneas.
- Persistência: por padrão, os dados ficam em memória. No browser, é possível persistir via IndexedDB usando o prefixo
idb://nodataDir. Em Node.js, o salvamento pode ser feito exportando o banco como dump SQL ou arquivo binário.
4. PGlite no browser: integração com aplicações web
Exemplo com React (hook personalizado)
import { useState, useEffect } from 'react';
import { PGlite } from '@electric-sql/pglite';
function usePGlite() {
const [db, setDb] = useState(null);
const [tarefas, setTarefas] = useState([]);
useEffect(() => {
const init = async () => {
const instance = new PGlite({ dataDir: 'idb://app-tarefas' });
await instance.exec(`
CREATE TABLE IF NOT EXISTS tarefas (
id SERIAL PRIMARY KEY,
texto TEXT NOT NULL,
concluida BOOLEAN DEFAULT FALSE
);
`);
setDb(instance);
carregarTarefas(instance);
};
init();
}, []);
const carregarTarefas = async (instance) => {
const result = await instance.query('SELECT * FROM tarefas ORDER BY id;');
setTarefas(result.rows);
};
const adicionarTarefa = async (texto) => {
await db.exec(`INSERT INTO tarefas (texto) VALUES ($1);`, [texto]);
carregarTarefas(db);
};
return { tarefas, adicionarTarefa };
}
Estratégia de backup e restore
// Exportar banco como SQL dump
const dump = await db.dumpDataAsSQL();
const blob = new Blob([dump], { type: 'application/sql' });
const url = URL.createObjectURL(blob);
// Importar dump
await db.exec(dump);
5. PGlite em Node.js: ambientes serverless e testes
Exemplo com Vitest
import { describe, it, expect, beforeAll } from 'vitest';
import { PGlite } from '@electric-sql/pglite';
let db;
beforeAll(async () => {
db = new PGlite();
await db.exec(`
CREATE TABLE produtos (
id SERIAL PRIMARY KEY,
nome VARCHAR(100),
preco DECIMAL(10,2)
);
`);
await db.exec(`
INSERT INTO produtos (nome, preco) VALUES
('Teclado', 150.00),
('Mouse', 80.00),
('Monitor', 1200.00);
`);
});
it('deve retornar produtos com preço acima de 100', async () => {
const result = await db.query('SELECT * FROM produtos WHERE preco > 100;');
expect(result.rows.length).toBe(2);
expect(result.rows[0].nome).toBe('Teclado');
});
Uso em Cloudflare Workers
// worker.js
import { PGlite } from '@electric-sql/pglite';
export default {
async fetch(request) {
const db = new PGlite();
await db.exec('CREATE TABLE IF NOT EXISTS visitas (id SERIAL, data TIMESTAMP DEFAULT NOW());');
await db.exec('INSERT INTO visitas DEFAULT VALUES;');
const count = await db.query('SELECT COUNT(*) as total FROM visitas;');
return new Response(JSON.stringify(count.rows[0]), {
headers: { 'Content-Type': 'application/json' }
});
}
};
6. Comparação com alternativas: SQL.js, DuckDB-WASM e SQLite em Node
| Característica | PGlite | SQL.js (SQLite WASM) | DuckDB-WASM |
|---|---|---|---|
| Sintaxe SQL | PostgreSQL | SQLite | DuckDB (SQL extendido) |
| Tamanho do binário | ~12MB | ~1.5MB | ~8MB |
| Tipos de dados | PostgreSQL completos | SQLite (limitados) | Tipos analíticos |
| Transações ACID | Sim | Sim | Sim |
| Suporte a JSON | Nativo (JSONB) | Limitado | Nativo |
| Ideal para | OLTP, apps web | Embarcado leve | Analytics, OLAP |
O PGlite se destaca quando você precisa de fidelidade ao ecossistema PostgreSQL — funções como array_agg, string_agg, ROW_NUMBER() e tipos JSONB funcionam exatamente como no PostgreSQL tradicional.
7. Limitações, performance e boas práticas
Performance comparativa
// Benchmark simples: inserir 1000 registros
console.time('pglite-insert');
for (let i = 0; i < 1000; i++) {
await db.exec(`INSERT INTO teste (valor) VALUES ($1);`, [i]);
}
console.timeEnd('pglite-insert');
// ~150ms no Node.js vs ~30ms no PostgreSQL nativo
Limitações conhecidas
- Sem suporte a triggers avançados ou funções escritas em PL/pgSQL que dependam de bibliotecas C
- Sem replicação nativa ou autenticação de usuários
- Operações DDL pesadas (ALTER TABLE em tabelas grandes) podem causar pausas perceptíveis no browser
Boas práticas
// Sempre use transações para operações atômicas
await db.exec('BEGIN;');
try {
await db.exec('UPDATE contas SET saldo = saldo - 100 WHERE id = 1;');
await db.exec('UPDATE contas SET saldo = saldo + 100 WHERE id = 2;');
await db.exec('COMMIT;');
} catch (e) {
await db.exec('ROLLBACK;');
}
// Gerencie o ciclo de vida: feche a conexão quando não for mais necessária
await db.close();
8. O futuro do PGlite e casos de uso emergentes
O roadmap do PGlite inclui:
- Suporte a extensões WASM customizadas (permitindo portar extensões PostgreSQL para o browser)
- Melhorias de performance via instruções SIMD no WebAssembly
- Persistência otimizada com snapshotting incremental
Casos emergentes já em desenvolvimento:
- PWAs com dados relacionais offline: aplicações de inventário, CRM mobile e editores colaborativos
- Ferramentas de BI no navegador: dashboards que executam consultas SQL diretamente nos dados do usuário
- Ambientes educacionais: plataformas de aprendizado de SQL que rodam PostgreSQL real no próprio navegador
Para contribuir, acesse o repositório oficial no GitHub e participe da comunidade no Discord do ElectricSQL.
Referências
- Documentação oficial do PGlite — Guia completo de instalação, API e exemplos práticos
- Repositório GitHub do PGlite — Código fonte, issues e roadmap oficial
- Artigo: "PostgreSQL no navegador com PGlite" no Dev.to — Tutorial detalhado com exemplos de integração React
- Benchmarks PGlite vs SQL.js vs DuckDB-WASM — Comparação oficial de performance entre as soluções WASM
- Exemplo de PWA com PGlite e IndexedDB — Aplicação completa de tarefas offline com sincronização
- Documentação do ElectricSQL sobre sincronização — Como usar PGlite com replicação para backend PostgreSQL
- Tutorial: Testes unitários com PGlite e Vitest — Guia prático para substituir bancos mock em testes