javascript

PGlite — полноценный Postgres-сервер на WASM. Работает прямо в браузере и Node.js

  • понедельник, 13 января 2025 г. в 00:00:03
https://habr.com/ru/articles/873112/

Безумные штуки иногда можно найти в интернете. Листая 2024 JavaScript rising stars (https://risingstars.js.org/2024/en#section-all) обнаружил там удивительного зверя - Postgres скомпилированный через emcc в WASM версию, и допиленный до состояния, когда его можно запустить внутри JS-процесса (браузер/Node.js/Bun/etc).

PGlite уже упоминался на Хабре (https://habr.com/ru/companies/postgrespro/articles/828950/), но я решил, что он так крут, что заслуживает отдельной небольшой статьи.

TL;DR: Представьте себе полноценный PostgreSQL, работающий в браузере (или в Node.js, Bun, Deno) без необходимости поднимать отдельный сервер или встраивать Linux-образ. Проект PGlite реализует эту идею - всего лишь 3 МБ (в сжатом виде), и причем, с поддержкой дефолтных популярных расширений, типа pgvector.

Что это такое?

PGlite - это PostgreSQL скомпилированный в WebAssembly и упакованный в простую TypeScript/JavaScript-библиотеку. Его ключевая фишка - отсутствие "линуксового" виртуального окружения, то есть вы не тянете за собой целый образ OS. В результате получаем:

  • Минимальный размер — около 3 МБ в сжатом виде.

  • Удобный API — просто импортируете библиотеку и вызываете методы для работы с базой. Можно подключить вашу любимую ORMку типа Drizzle/TypeORM.

  • Поддержку расширений — в поставку уже входят некоторые популярные плагины, вроде pgvector.

Можно использовать PGlite как обычную in-memory базу (данные хранятся в памяти и пропадают при перезапуске), а можно включить постоянное хранение - IndexedDB в браузере или файловую систему в Node.js/Bun/Deno.

Как это всё вообще работает?

Обычно PostgreSQL работает в многопоточном режиме помощью процессов: при новом подключении форкается отдельный процесс, чтобы обрабатывать запросы. Но в Emscripten (C->WASM) и в JavaScript у нас нет возможности делать fork().

Однако у Postgres есть single user mode. Он изначально задумывался для рекавери, восстановления БД, но отлично подошёл как раз для PGlite: чтобы не плодить процессы и просто работать в одном потоке. Эту часть и взяли за основу, адаптировав ввод-вывод и окружение под WebAssembly.

В результате PGlite запускается как обычная библиотека, а дальше вы пользуетесь SQL так же, как и в «настоящем» Postgres. Разница лишь в том, что доступен только один коннект и один юзер.

Как пользоваться этим чудом в браузере?

Для начала, надо поставить пакет:

npm install @electric-sql/pglite

или импортировать, если вы в браузере:

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

Далее - классический пример in-memory базы:

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

const db = new PGlite();
const result = await db.query("SELECT 'Привет Хабр!' AS message;");
console.log(result); 
// -> { rows: [ { message: "Привет Хабр!" } ] }

Чтобы сохранять данные между перезагрузками (в IndexedDB), нужно при инициализации указать путь:

const db = new PGlite("idb://my-pgdata");
// Данные останутся в IndexedDB

Как пользоваться в Node/Bun/Deno?

Тут всё схоже: ставим пакет и подключаем:

npm install @electric-sql/pglite

или (для любителей булочек):

bun install @electric-sql/pglite

или (для любителей секьюрных сред):

deno add npm:@electric-sql/pglite

базовое использование точно такое же, как и в браузере:

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

const db = new PGlite(); // in-memory
await db.query("SELECT 'Привет, Хабр!' AS message;");

но на сервере у нас есть возможность указать путь к файлу, чтобы сохранять данные:

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

Теперь ваши таблицы и записи будут лежать в локальной файловой системе и не потеряются после перезапуска.

Зачем оно вообще нужно?

  • Быстрое тестирование и прототипирование. Не всегда хочется тащить громоздкий Postgres-сервер ради пары тестовых запросов. Идеально, чтобы гонять тесты - не надо громоздить docker-Postgres ради того, чтобы прогнать юнит-тесты - PGlite идеально подойдёт для мокинга реальной базы (потому что он и есть реальная база :)

  • Демо и учебные проекты. Отлично подходит, когда нужно показать работу базы данных без установки дополнительного ПО.

  • «Local-first» приложения. Можно хранить данные прямо в браузере, а потом синхронизировать их, когда появится соединение с внешней СУБД.

  • Песочницы и эксперименты. Удобно запускать SQL-скрипты и эксперименты в изолированной среде, не трогая основную базу данных.

Примеры кода

Ниже несколько коротких демок:

// Создаём таблицу
await db.query(`
  CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL
  );
`);

// Вставляем данные
await db.query("INSERT INTO users (name) VALUES ('Васян'), ('Лёха')");

// Смотрим, сколько записей
const { rows } = await db.query("SELECT COUNT(*) AS total FROM users");
console.log(rows); 
// -> [ { total: "2" } ]

// А теперь вернём сами записи
const { rows: allUsers } = await db.query("SELECT * FROM users");
console.log(allUsers);
// -> [ { id: 1, name: "Васян" }, { id: 2, name: "Лёха" } ]

И всё это работает прямо в вашем браузере или Node.js. Да, я сказал это уже раз 100, но блин, это же реально просто офигенно :)

Итого

PGlite - это простой и клёвый способ получить всю мощь Postgres в одну строчку кода. Подходит для быстрых экспериментов, учебных задач, легковесных демо и «офлайн-приложений». Если вам нужен «настоящий PostgreSQL», но без виртуальных машин, докеров, и прочих обвесок - это именно оно.

P.S. Сборка Postgres в WebAssembly во многом основана на работе Stas Kelvich из Neon. Заходите в репозиторий, если интересно, как «под капотом» выглядит форк PostgreSQL под WASM.

P.P.S. 2025 год на дворе, как же без рекламы канала? собсна, я тоже сделал тг-канал. там я отвратительно себя веду, много матерюсь, но часто пишу всякое полезное про техно-фаундерство, AI, и прочее: как про мои opensource-либы, так и про то, как я укус за укусом прогрызаю свой путь в этом вашем медиа-пространстве. Подписывайтесь!