← All articles
Laptop screen displaying code with glasses and mug.

PGlite: Running PostgreSQL in the Browser and Node.js

Database 2026-03-21 · 4 min read pglite postgresql webassembly browser-database local-first

PGlite is a lightweight WebAssembly build of PostgreSQL that runs entirely in the browser, Node.js, or Bun. It gives you a real Postgres instance — not a SQL-like subset, not a compatibility layer — actual PostgreSQL with its query planner, type system, and extension support. The whole thing ships as an npm package under 3MB gzipped.

Photo by Daniil Komov on Unsplash

Why PGlite Exists

SQLite has been the go-to embedded database for decades, and projects like sql.js brought it to the browser. But if your backend runs PostgreSQL, using SQLite on the frontend means dealing with SQL dialect differences, missing features like JSONB operators, and schema divergence between your development and production databases.

PGlite solves this by running the same database engine everywhere. Your queries, migrations, and schemas work identically whether they execute in a user's browser tab or against your production Postgres cluster.

Getting Started

Install PGlite from npm:

npm install @electric-sql/pglite

Create an in-memory database and run queries:

import { PGlite } from "@electric-sql/pglite";

const db = new PGlite();

await db.exec(`
  CREATE TABLE tasks (
    id SERIAL PRIMARY KEY,
    title TEXT NOT NULL,
    completed BOOLEAN DEFAULT false,
    created_at TIMESTAMPTZ DEFAULT NOW()
  )
`);

await db.exec(`
  INSERT INTO tasks (title) VALUES ('Write documentation')
`);

const result = await db.query("SELECT * FROM tasks WHERE completed = false");
console.log(result.rows);

That's real PostgreSQL syntax — SERIAL, TIMESTAMPTZ, DEFAULT NOW() — running in your JavaScript runtime with no server process.

Persistent Storage

In-memory databases disappear when the page reloads. PGlite supports multiple storage backends for persistence:

// IndexedDB in the browser
const db = new PGlite("idb://my-app-db");

// File system in Node.js/Bun
const db = new PGlite("./data/my-local-db");

The IndexedDB backend stores the entire Postgres data directory in the browser's storage. This means your database survives page refreshes and browser restarts, with the same durability guarantees as any other IndexedDB data.

Real PostgreSQL Features

PGlite isn't a toy subset. You get the features that make PostgreSQL the preferred database for complex applications:

JSONB with operators:

SELECT * FROM events
WHERE metadata @> '{"type": "click"}'
ORDER BY metadata->>'timestamp' DESC;

Common Table Expressions:

WITH RECURSIVE category_tree AS (
  SELECT id, name, parent_id, 0 AS depth
  FROM categories WHERE parent_id IS NULL
  UNION ALL
  SELECT c.id, c.name, c.parent_id, ct.depth + 1
  FROM categories c
  JOIN category_tree ct ON c.parent_id = ct.id
)
SELECT * FROM category_tree ORDER BY depth, name;

Window functions, lateral joins, full-text search — they all work. If PostgreSQL supports it and it doesn't require OS-level features (like LISTEN/NOTIFY or replication slots), PGlite runs it.

Extensions

PGlite supports loading PostgreSQL extensions as WebAssembly modules. The pgvector extension is particularly useful for AI applications:

import { PGlite } from "@electric-sql/pglite";
import { vector } from "@electric-sql/pglite/contrib/pgvector";

const db = new PGlite({ extensions: { vector } });

await db.exec("CREATE EXTENSION IF NOT EXISTS vector");
await db.exec(`
  CREATE TABLE documents (
    id SERIAL PRIMARY KEY,
    content TEXT,
    embedding vector(384)
  )
`);

This gives you vector similarity search running entirely in the browser — no API calls to a vector database service needed.

Live Queries

PGlite has a reactive query layer that re-runs queries when underlying data changes:

const unsubscribe = await db.live.query(
  "SELECT * FROM tasks WHERE completed = false ORDER BY created_at DESC",
  [],
  (result) => {
    // Called whenever the result set changes
    renderTaskList(result.rows);
  }
);

This pairs well with frontend frameworks. Instead of manually invalidating caches or re-fetching after mutations, your UI updates automatically when any write affects the query result.

Development and Testing

One of the strongest use cases for PGlite is replacing Docker-based PostgreSQL in development and testing:

import { describe, it, beforeEach } from "vitest";
import { PGlite } from "@electric-sql/pglite";

describe("user repository", () => {
  let db: PGlite;

  beforeEach(async () => {
    db = new PGlite(); // Fresh database per test
    await db.exec(migrationSQL);
  });

  it("creates a user with default role", async () => {
    await db.exec("INSERT INTO users (email) VALUES ('[email protected]')");
    const { rows } = await db.query("SELECT role FROM users WHERE email = $1", ["[email protected]"]);
    expect(rows[0].role).toBe("member");
  });
});

Each test gets a fresh, isolated PostgreSQL instance that starts in milliseconds. No Docker containers, no port conflicts, no cleanup scripts. Your CI pipeline gets faster and your test isolation gets stronger.

Performance Considerations

PGlite runs single-threaded in WebAssembly, so it won't match a native PostgreSQL server for heavy workloads. Expect roughly 2-5x slower query execution compared to native Postgres. For typical application queries against datasets under 100MB, this is barely noticeable.

Where it struggles: bulk inserts of millions of rows, complex analytical queries over large datasets, or anything that benefits from PostgreSQL's parallel query execution. For those workloads, you still want a real server.

Where it shines: interactive applications with moderate data, test suites that need real Postgres semantics, offline-capable apps, and prototyping without infrastructure.

When to Use PGlite

Good fit:

Not the right tool:

PGlite fills a specific gap: when you need real PostgreSQL semantics but don't want (or can't have) a server process. For local-first applications and testing, it removes an entire layer of infrastructure complexity while keeping full SQL compatibility with your production database.