Web Development Databases 1 min read

PGlite: Postgres in WASM Is Here (And It's Only 3MB)

B
Bright Coding
Author
Share:
PGlite: Postgres in WASM Is Here (And It's Only 3MB)
Advertisement

PGlite: Postgres in WASM Is Here (And It's Only 3MB)

What if I told you that the world's most trusted relational database now fits in your browser tab—no server, no Docker, no installation nightmare?

For decades, developers have accepted a brutal trade-off: want the power of PostgreSQL? Better fire up a server, configure ports, manage users, and pray your pg_hba.conf doesn't betray you at 2 AM. Need offline capabilities for your web app? Say hello to IndexedDB's Byzantine API or shoehorn SQLite into a stack it was never designed for. The pain is real, and every developer who's wrestled with pg_dump migrations or explained to a client why their "simple" app needs a $50/month database server knows it.

But what if PostgreSQL itself could run inside your JavaScript environment? Not a toy replacement. Not a compatibility layer. Actual Postgres. In WebAssembly. At 3MB gzipped.

Enter PGlite from ElectricSQL—the project that's making "Postgres in WASM" go viral across developer communities. This isn't another experimental hack that falls apart when you need JOINs or ACID transactions. This is the real deal: a WASM build of PostgreSQL packaged as a TypeScript client library, running seamlessly in browsers, Node.js, Bun, and Deno. No Linux VM. No container overhead. Just pure, embeddable Postgres with real-time, reactive bindings for building local-first applications.

If you're still relying on complex backend infrastructure for every project, you might be doing it wrong. Let me show you why PGlite is about to change how you architect applications forever.


What Is PGlite?

PGlite is an embeddable PostgreSQL distribution compiled to WebAssembly (WASM) and wrapped in a clean TypeScript API. Born from ElectricSQL's mission to enable reactive, realtime, local-first applications, PGlite represents a fundamental breakthrough in database portability.

The project builds upon critical foundational work by Stas Kelvich of Neon, whose Postgres fork solved the core compilation challenges. ElectricSQL's team then transformed this into a production-ready developer experience that feels native to modern JavaScript ecosystems.

Here's why PGlite is trending right now:

  • The local-first movement is exploding: Developers are rejecting cloud-only architectures in favor of resilient, offline-capable applications
  • Edge computing demands lighter databases: Traditional Postgres deployments are overkill for edge functions and serverless environments
  • WebAssembly maturity: WASM has finally reached the performance and compatibility threshold for serious database workloads
  • AI/ML applications need vector search everywhere: PGlite ships with pgvector support, bringing semantic search to browsers and edge runtimes

Unlike previous "Postgres in browser" attempts that relied on emulating entire Linux virtual machines (slow, bloated, fragile), PGlite takes a radically different approach. It leverages PostgreSQL's built-in single-user mode—originally designed for bootstrapping and recovery—and constructs a custom I/O pathway that bridges this mode with JavaScript environments. The result? A genuine Postgres process running as a single WASM module, communicating directly with your TypeScript code.

Current status: Alpha (actively developed, Apache 2.0 / PostgreSQL dual-licensed). The project has already garnered significant GitHub attention and maintains an active Discord community.


Key Features That Make PGlite Insane

Let's dissect what makes this 3MB package so technically remarkable:

True PostgreSQL Compatibility

PGlite isn't a reimplementation—it's actual PostgreSQL 15+ compiled to WASM. This means:

  • Full SQL compliance (complex JOINs, CTEs, window functions, triggers)
  • ACID transactions with proper isolation levels
  • Procedural languages (PL/pgSQL)
  • Custom extensions including pgvector for AI embeddings

Multi-Runtime Support

One API, four environments:

  • Browser: Direct import via npm or CDN (JSDelivr)
  • Node.js: Native filesystem persistence
  • Bun: Optimized for Bun's performance characteristics
  • Deno: Native TypeScript support with deno add

Flexible Persistence Models

┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐
│   In-Memory     │  │  Filesystem     │  │   IndexedDB     │
│   (Ephemeral)   │  │  (Node/Bun/Deno)│  │   (Browser)     │
│                 │  │                 │  │                 │
│  new PGlite()   │  │ new PGlite(     │  │ new PGlite(     │
│                 │  │   "./pgdata")   │  │   "idb://name") │
└─────────────────┘  └─────────────────┘  └─────────────────┘

Real-Time Reactive Bindings

This is where ElectricSQL's heritage shines. PGlite integrates with ElectricSQL's replication system, enabling:

  • Live query subscriptions that push updates to your UI
  • Automatic conflict resolution for multi-user scenarios
  • Seamless sync with backend Postgres when connectivity returns

Extension Ecosystem

Despite its tiny footprint, PGlite supports multiple Postgres extensions:

  • pgvector: Vector similarity search for AI applications
  • PostGIS (roadmap): Geospatial queries
  • Custom WASM-compiled extensions possible

Size Optimization

At ~3MB gzipped, PGlite defies expectations. For comparison:

  • Typical Docker Postgres image: ~150MB+
  • Previous WASM Postgres attempts: 50MB+
  • SQLite WASM builds: ~1MB (but not Postgres-compatible)

Use Cases Where PGlite Destroys the Competition

1. Offline-First Web Applications

Build Progressive Web Apps with genuine relational power. Your users get:

  • Full SQL querying in service workers
  • Complex reporting that IndexedDB simply cannot handle
  • Seamless background sync when connectivity returns

Real scenario: A field sales app where representatives build complex quotes with multi-table relationships, completely offline, then sync when they hit WiFi.

2. AI-Powered Browser Experiences

With pgvector built in, PGlite enables:

  • Client-side semantic search over private documents
  • Real-time embedding comparisons without API calls
  • Vector databases in privacy-sensitive applications (healthcare, legal)

Real scenario: A HIPAA-compliant medical research tool where vector similarity runs entirely in the browser—zero data leaves the device.

3. Edge-Native Microservices

Deploy Postgres logic to Cloudflare Workers, Vercel Edge, or Deno Deploy:

  • Sub-100ms cold starts (vs. seconds for containerized Postgres)
  • Geographic distribution without database replication complexity
  • Per-request isolated databases for multi-tenant SaaS

4. Development & Testing Environments

Eliminate "works on my machine" database issues:

  • Each test gets a fresh, fast in-memory Postgres
  • No Docker daemon required for CI pipelines
  • Parallel test execution without port conflicts

5. Embedded Analytics Dashboards

Ship interactive BI directly in your application:

  • Complex aggregations without backend round-trips
  • Real-time filtering on million-row datasets
  • Zero infrastructure for customer-facing analytics

Step-by-Step Installation & Setup Guide

Browser Setup

Via npm (recommended for bundlers):

npm install @electric-sql/pglite

Via CDN (instant prototyping):

import { PGlite } from "https://cdn.jsdelivr.net/npm/@electric-sql/pglite/dist/index.js";

Basic in-memory database:

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

// Create ephemeral database—data vanishes on page refresh
const db = new PGlite();

// Verify it's working
const result = await db.query("select 'Hello world' as message;");
console.log(result.rows[0].message); // "Hello world"

Persistent browser storage with IndexedDB:

// "idb://" prefix triggers IndexedDB persistence
const db = new PGlite("idb://my-pgdata");

// Your schema and data survive page refreshes
await db.query(`
  CREATE TABLE IF NOT EXISTS todos (
    id SERIAL PRIMARY KEY,
    task TEXT NOT NULL,
    completed BOOLEAN DEFAULT FALSE
  )
`);

Node.js Setup

npm install @electric-sql/pglite
import { PGlite } from "@electric-sql/pglite";
import { join } from "path";

// In-memory for tests or ephemeral processing
const memDb = new PGlite();

// Filesystem persistence for actual applications
const dataDir = join(process.cwd(), "pgdata");
const persistentDb = new PGlite(dataDir);

// The directory is created automatically; 
// Postgres's native storage format is used directly

Bun Setup

bun install @electric-sql/pglite

Bun's superior WASM performance makes it particularly compelling for PGlite workloads. The API remains identical:

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

const db = new PGlite("./bun-pgdata");
// Bun's faster I/O translates to quicker persistence operations

Deno Setup

deno add npm:@electric-sql/pglite
import { PGlite } from "npm:@electric-sql/pglite";

const db = new PGlite("./deno-pgdata");

Environment Considerations

Environment Persistence Best For
Browser (no prefix) Memory only Prototyping, tests
Browser (idb://) IndexedDB Production PWAs
Node.js/Bun/Deno (path) Filesystem Servers, CLI tools
Node.js (no args) Memory Unit tests, temp processing

REAL Code Examples from the Repository

Let's examine production-ready patterns using actual code from PGlite's documentation:

Advertisement

Example 1: Basic Query Execution

The simplest possible PGlite usage, straight from the README:

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

const db = new PGlite();
await db.query("select 'Hello world' as message;");
// -> { rows: [ { message: "Hello world" } ] }

What's happening here?

  • new PGlite() instantiates a WASM Postgres process in single-user mode
  • The constructor returns immediately, but internal WASM initialization is async
  • db.query() queues until initialization completes, then executes via the custom I/O pathway
  • Results mirror the pg Node.js driver format: { rows, fields, command, rowCount }

This API compatibility means migrating existing Node.js/Postgres code requires minimal changes.

Example 2: Persistent Browser Database

const db = new PGlite("idb://my-pgdata");

Deep dive: The idb:// protocol scheme triggers PGlite's IndexedDB storage adapter. Under the hood:

  1. Postgres's native heap and index files are serialized to ArrayBuffers
  2. These buffers are chunked and stored in IndexedDB object stores
  3. A write-ahead log (WAL) is maintained for crash recovery
  4. On initialization, the latest consistent snapshot is reconstructed

This isn't a SQL-to-IndexedDB translation layer—it's genuine Postgres files living in browser storage.

Example 3: Filesystem Persistence in Node.js

const db = new PGlite("./path/to/pgdata");

Critical implementation detail: PGlite writes actual Postgres data directory structures. You can:

  • Open this directory with native psql (same major version)
  • Use pg_dump for backups
  • Replicate to cloud Postgres via WAL shipping

This interoperability is impossible with SQLite or other embedded alternatives.

Example 4: Building a Reactive Todo App

Extending the README patterns for real-world usage:

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

class TodoStore {
  constructor() {
    // Use IndexedDB for durable client-side storage
    this.db = new PGlite("idb://todos-app");
    this.initPromise = this.initializeSchema();
  }

  async initializeSchema() {
    // Real Postgres DDL—no compromises
    await this.db.query(`
      CREATE TABLE IF NOT EXISTS todos (
        id SERIAL PRIMARY KEY,
        title TEXT NOT NULL CHECK (length(title) > 0),
        priority INTEGER DEFAULT 1 CHECK (priority BETWEEN 1 AND 5),
        created_at TIMESTAMPTZ DEFAULT NOW(),
        completed_at TIMESTAMPTZ
      );
      
      -- Real indexes for performance
      CREATE INDEX IF NOT EXISTS idx_todos_priority 
        ON todos(priority DESC, created_at DESC);
    `);
  }

  async addTodo(title, priority = 1) {
    await this.initPromise;
    
    const result = await this.db.query(
      `INSERT INTO todos (title, priority) 
       VALUES ($1, $2) 
       RETURNING *`,
      [title, priority]
    );
    
    return result.rows[0]; // Full row with generated id and timestamp
  }

  async getTodosByPriority(minPriority = 1) {
    await this.initPromise;
    
    // Complex queries that IndexedDB simply cannot express
    const result = await this.db.query(`
      SELECT 
        id,
        title,
        priority,
        CASE 
          WHEN completed_at IS NOT NULL THEN 'done'
          WHEN created_at < NOW() - INTERVAL '7 days' THEN 'stale'
          ELSE 'active'
        END as status
      FROM todos
      WHERE priority >= $1
      ORDER BY 
        completed_at IS NULL DESC,  -- Active first
        priority DESC,
        created_at DESC
    `, [minPriority]);
    
    return result.rows;
  }
}

Example 5: Vector Search with pgvector

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

const db = new PGlite("idb://semantic-search");

// pgvector extension is pre-compiled in PGlite
await db.query(`CREATE EXTENSION IF NOT EXISTS vector`);

// Store 1536-dimensional OpenAI-style embeddings
await db.query(`
  CREATE TABLE documents (
    id SERIAL PRIMARY KEY,
    content TEXT NOT NULL,
    embedding vector(1536)
  );
  
  -- IVFFlat index for approximate nearest neighbor search
  CREATE INDEX ON documents 
    USING ivfflat (embedding vector_cosine_ops)
    WITH (lists = 100);
`);

// Insert with embedding (generated via your preferred API)
await db.query(`
  INSERT INTO documents (content, embedding)
  VALUES ($1, $2::vector)
`, ["PGlite documentation", "[0.023, -0.051, ...]"]);

// Semantic similarity search—entirely client-side
const similar = await db.query(`
  SELECT content, 1 - (embedding <=> $1::vector) as similarity
  FROM documents
  ORDER BY embedding <=> $1::vector
  LIMIT 5
`, [queryEmbedding]);

Advanced Usage & Best Practices

Connection Pooling Patterns

Since PGlite is single-user/connection, implement application-level queuing:

class PGlitePool {
  constructor(dbPath, poolSize = 1) {
    // PGlite currently supports single connection;
    // poolSize reserved for future multi-tab support
    this.db = new PGlite(dbPath);
    this.queue = [];
    this.busy = false;
  }
  
  async query(sql, params) {
    if (this.busy) {
      await new Promise(resolve => this.queue.push(resolve));
    }
    this.busy = true;
    try {
      return await this.db.query(sql, params);
    } finally {
      this.busy = false;
      if (this.queue.length) this.queue.shift()();
    }
  }
}

WASM Preloading for Performance

<!-- Preload WASM in HTML head for faster startup -->
<link rel="preload" href="/node_modules/@electric-sql/pglite/dist/postgres.wasm" as="fetch" crossorigin>

Memory Management

In-memory databases consume WASM heap (default ~128MB). For large datasets:

// Increase heap for data-intensive operations
const db = new PGlite({ 
  // Future API; currently modify compile flags
});

Migration Strategies

Since PGlite uses native Postgres format, leverage existing tools:

# Export from cloud Postgres
pg_dump -Fc mydb > backup.dump

# Import into PGlite filesystem directory
# (requires pg_restore with same version)

Comparison with Alternatives

Feature PGlite sql.js (SQLite WASM) Postgres.js + Docker IndexedDB
SQL Standard PostgreSQL SQLite PostgreSQL None (NoSQL)
Size ~3MB gzipped ~1MB gzipped ~150MB+ Built-in
ACID Transactions ✅ Full ✅ Full ✅ Full ⚠️ Limited
Complex Queries ✅ Excellent ✅ Good ✅ Excellent ❌ Poor
Vector Search ✅ pgvector ❌ No extension ✅ pgvector ❌ Manual
Browser Native ✅ Yes ✅ Yes ❌ No ✅ Yes
Node.js Native ✅ Yes ✅ Yes ✅ Via TCP ❌ No
Backend Compatibility ✅ Native format ❌ Requires conversion ✅ N/A ❌ N/A
Real-time Sync ✅ ElectricSQL ❌ Manual ⚠️ Complex ❌ Manual
Multi-connection ❌ Single ❌ Single ✅ Unlimited ✅ Unlimited

When to choose PGlite over sql.js: You need PostgreSQL-specific features (pgvector, specific extensions), backend compatibility, or plan to sync with cloud Postgres.

When to choose sql.js over PGlite: Absolute minimal size, simple relational needs, no Postgres-specific requirements.

When to choose traditional Postgres: Multi-user concurrent access, massive datasets exceeding browser memory, complex replication topologies.


FAQ

Is PGlite production-ready?

PGlite is currently alpha. It's ideal for prototypes, offline-first apps, and development environments. Production use should include thorough testing for your specific workload. Track progress on the roadmap.

How does PGlite differ from sql.js?

While both are WASM databases, PGlite runs actual PostgreSQL versus SQLite in sql.js. This means native Postgres extensions, backend compatibility, and ElectricSQL's reactive sync capabilities.

Can I connect PGlite to my existing cloud Postgres?

Direct TCP connections aren't possible (browser security + WASM limitations). However, ElectricSQL's replication protocol enables sync between PGlite and backend Postgres instances.

What's the maximum database size?

Constrained by available memory (in-memory) or IndexedDB quota (typically 50-60% of disk space in browsers). For large datasets, use filesystem persistence in Node.js/Bun/Deno.

Does PGlite support all Postgres extensions?

Currently pgvector and core extensions. Additional extensions require WASM compilation—contributions welcome! Check the build documentation for extension porting guides.

How do I debug PGlite queries?

Standard Postgres techniques apply: EXPLAIN ANALYZE, query logs (configurable), and the same error messages you'd expect from server Postgres.

Is there ORM support?

Any PostgreSQL-compatible ORM (Drizzle, Kysely, raw pg patterns) works with PGlite's query interface. Prisma support is being explored.


Conclusion

PGlite represents something rare in database technology: a genuine paradigm shift that doesn't sacrifice capability for convenience. By compiling PostgreSQL to a 3MB WASM module, ElectricSQL has dissolved the boundary between "server databases" and "client databases"—a boundary that has constrained web architecture for twenty years.

The implications are staggering. AI applications with private, client-side vector search. Offline-first CRMs with complex relational integrity. Edge functions running genuine Postgres without cold start penalties. Development environments where npm install is your entire database setup.

Is PGlite perfect? Not yet—alpha status means rough edges, single-connection limits, and evolving APIs. But the trajectory is unmistakable. This is how databases should work: present where you need them, absent where you don't, interoperable across every runtime that matters.

Your move. Stop provisioning servers for every prototype. Stop accepting "close enough" from browser storage APIs. Stop pretending SQLite is the only embeddable option.

👉 Star PGlite on GitHub — contribute, experiment, and join the movement toward truly portable PostgreSQL. The future of databases isn't in the cloud. It's wherever your code runs.


Built with passion by ElectricSQL. Sponsored by Blacksmith for accelerated CI builds.

Advertisement

Comments (0)

No comments yet. Be the first to share your thoughts!

Leave a Comment

Apps & Tools Open Source

Apps & Tools Open Source

Bright Coding Prompt

Bright Coding Prompt

Categories

Advertisement
Advertisement
Advertisement