Building Local-First Apps with PGlite: The Power of Postgres in Your Browser

For decades, web development has been defined by a fundamental friction: the physical distance between the client's state and the server's database. We have built towers of complexity—loading spinners, optimistic UI updates, and intricate caching layers—just to mask the latency of HTTP requests. We treat the browser as a dumb terminal that merely renders data fetched from a "real" source of truth miles away.

This paradigm is shifting. The emergence of Local-First architecture offers a modern solution to these friction points, flipping the traditional model on its head. In a local-first application, the primary data source is on the user's device, delivering instant interactions and seamless offline capability.

Enter PGlite. Developed by ElectricSQL, PGlite is a lightweight, WebAssembly (Wasm) build of Postgres packaged into a TypeScript client. It isn't a simulation; it is a full Postgres instance running entirely within the browser process.

PGlite Local-First Architecture Diagram
Figure 1: PGlite Architecture Overview

PGlite revolutionizes frontend development by allowing developers to run a full, reactive Postgres database directly in the browser. This enables the creation of zero-latency applications with radically simplified state management, bringing the power of the world's most advanced open-source database to the client side.

What is PGlite and How Does it Work?

At its core, PGlite is a repackaging of the PostgreSQL database engine into a format that web browsers can understand and execute. It bridges the gap between server-grade data management and client-side execution environments.

The Magic of WebAssembly (Wasm)

PGlite leverages WebAssembly (Wasm) to run compiled C code within the JavaScript runtime. The team at ElectricSQL has successfully compiled the actual Postgres source code into Wasm. This is a critical distinction: PGlite is not a JavaScript rewrite of Postgres logic, nor is it a mock interface like pg-mem. It is the genuine Postgres engine, executing the same internal logic as a server-side instance, but sandboxed within your browser tab. This allows it to run with surprising performance, handling complex queries and transactions locally.

PGlite vs. SQLite/Wasm

Historically, if a developer wanted a SQL database in the browser, SQLite (via Wasm) was the only viable option. While SQLite is excellent, it forces a context switch. Developers often use Postgres on the server and SQLite on the client, leading to "dialect fatigue"—the mental overhead of remembering which SQL features (like specific date functions or JSON operators) are supported in which environment.

PGlite eliminates this discrepancy. You can use the exact same database engine on both ends of the wire. Furthermore, PGlite brings the rich ecosystem of Postgres extensions to the browser. Features like pgvector can now be run client-side, enabling vector similarity searches for AI applications without ever hitting an API endpoint.

The Strategic Shift: Why Choose Local-First Architecture?

Adopting PGlite isn't just about changing databases; it's about adopting a Local-First architecture. In this model, the primary copy of the data lives on the client device, while the cloud serves effectively as a backup and synchronization relay.

  • Zero Latency: When your database is in-memory or persisted to IndexedDB within the browser, read and write operations do not traverse the network. Interactions feel instantaneous. There is no "request/response" cycle; there is simply an immediate state change. This responsiveness creates a user experience (UX) that feels more like a native desktop app than a website.
  • Offline Capabilities: In a local-first app, offline support is not an afterthought or a complex service worker retrofit—it is the default state. Because the database is local, the application works perfectly whether the user is in a tunnel, on a plane, or suffering from flaky Wi-Fi. The app remains fully functional, and data syncs when connectivity is restored.
  • Simplified State Management: Modern frontend development is often plagued by state management complexity (Redux, Zustand, Context API) designed to manage server data fetching. With PGlite, the database becomes the single source of truth for your UI. You can query the database directly from your components, significantly reducing the boilerplate code required to manage application state.

Technical Tutorial: Building with PGlite

Let's look at how to get a PGlite instance running in a standard web project.

Installation and Initialization

First, install the package via npm. It is lightweight and creates no external system dependencies.

npm install @electric-sql/pglite

Initializing the database is straightforward. You can import PGlite and instantiate it immediately in your JavaScript or TypeScript file.

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

// Initialize an ephemeral (in-memory) instance
const db = new PGlite();
console.log("PGlite started!");

Executing Queries

Interacting with PGlite uses standard SQL. The API is promise-based, making it easy to use with async/await.

async function initDB() {
  // Create a table
  await db.query(`
    CREATE TABLE IF NOT EXISTS todo (
      id SERIAL PRIMARY KEY,
      task TEXT,
      done BOOLEAN DEFAULT false
    );
  `);

  // Insert data using parameterized queries
  // Always use parameters ($1, $2) to prevent injection, even locally.
  await db.query("INSERT INTO todo (task, done) VALUES ($1, $2)", [
    "Install PGlite",
    true,
  ]);

  // Select data
  const result = await db.query("SELECT * FROM todo");
  console.log(result.rows);
  // Output: [{ id: 1, task: 'Install PGlite', done: true }]
}

Persisting Data

By default, the example above runs in memory. If the user refreshes the page, the data is lost. To make the app truly local-first, we need persistence. PGlite enables this by mapping the Postgres file system to the browser's IndexedDB.

To enable persistence, simply provide a data directory path prefixed with idb:// during initialization:

// This will persist data to IndexedDB under the name "my-app-db"
const db = new PGlite("idb://my-app-db");

Now, your data survives page reloads and browser restarts.

Integrating PGlite with Modern Frameworks

While raw SQL is powerful, modern web development relies on reactivity—the UI should update automatically when data changes.

Reactivity in React/Vue

PGlite supports the concept of "Live Queries." This allows components to subscribe to a query and re-render whenever the underlying data changes, eliminating the need to manually trigger refetches.

Here is a conceptual example of a React hook leveraging PGlite's subscription capabilities:

import { useState, useEffect } from 'react';

function useLiveQuery(db, sql) {
  const [data, setData] = useState([]);

  useEffect(() => {
    // Subscribe to the query
    const unsubscribe = db.live.query(sql, [], (results) => {
      setData(results.rows);
    });

    // Cleanup subscription on unmount
    return () => unsubscribe();
  }, [db, sql]);

  return data;
}

// Usage in a component
const TodoList = ({ db }) => {
  const todos = useLiveQuery(db, "SELECT * FROM todo ORDER BY id DESC");

  return (
    
    {todos.map(t =>
  • {t.task}
  • )}
); };

Handling Synchronization (The Future)

The final piece of the local-first puzzle is getting data off the device and syncing it with other users. While you can build custom REST endpoints to push local changes to a server, this is non-trivial to get right (handling conflict resolution, diffing, etc.).

This is where protocols like ElectricSQL come into play. Designed specifically to pair with PGlite, ElectricSQL handles seamless, bi-directional sync between your local PGlite instance and a central Postgres server. This provides the "multiplayer" functionality modern apps require without the developer needing to write complex sync logic.

Conclusion

PGlite represents a significant leap forward for web architecture. By bringing the full power of Postgres into the browser via WebAssembly, it bridges the historical gap between frontend reactivity and backend robustness.

The benefits are tangible: zero-latency interfaces, inherent offline resilience, and the simplicity of using standard SQL everywhere. For developers, it means less boilerplate code; for users, it means an app that feels instant and reliable.

If you are planning your next web application, I strongly encourage you to experiment with PGlite. The shift to local-first is not just a trend—it is a better way to build software.

Building secure, privacy-first tools means staying ahead of technology trends. At ToolShelf, we prioritize local-first tools that respect your privacy—your data never leaves your device unless you want it to.

Stay secure & happy coding,
— ToolShelf Team