PGlite: تشغيل PostgreSQL في المتصفح باستخدام WebAssembly

ماذا ستتعلم
في هذا الدليل التعليمي، ستكتشف كيفية استخدام PGlite، مشروع مفتوح المصدر من ElectricSQL يُترجم PostgreSQL إلى WebAssembly (WASM). بنهاية هذا الدليل، ستتمكن من:
- تشغيل قاعدة بيانات PostgreSQL كاملة في المتصفح بدون أي خادم
- حفظ البيانات عبر إعادة تحميل الصفحة باستخدام IndexedDB
- استخدام استعلامات تفاعلية حية تُحدّث واجهة المستخدم تلقائياً
- بناء تطبيق مهام (Todo) محلي أولاً باستخدام React و TypeScript
- فهم متى ولماذا يكون PGlite الخيار المناسب لمشروعك
المتطلبات المسبقة
قبل البدء، تأكد من توفر:
- Node.js 20+ مُثبّت على جهازك
- مدير حزم npm أو pnpm
- معرفة أساسية بـ React و TypeScript
- إلمام بـ SQL (SELECT، INSERT، UPDATE، DELETE)
- متصفح حديث (Chrome أو Firefox أو Edge)
لماذا PGlite؟
تحتاج تطبيقات الويب التقليدية إلى خادم قاعدة بيانات في الخلفية. PGlite يُغيّر هذه المعادلة بتضمين نسخة PostgreSQL كاملة مباشرة في بيئة JavaScript. إليك لماذا هذا مهم:
- صفر بنية تحتية: لا حاجة لتجهيز أو إعداد أو صيانة خادم قاعدة بيانات
- بدء فوري: تتهيأ قاعدة البيانات في أجزاء من الثانية داخل المتصفح
- دعم SQL كامل: على عكس IndexedDB أو localStorage، تحصل على SQL حقيقي مع الربط (joins) والفهارس ومعاملات JSON والإضافات
- العمل بدون اتصال: البيانات تعيش في المتصفح — تطبيقك يعمل بدون إنترنت
- استعلامات تفاعلية: نظام الاستعلامات الحية في PGlite يدفع التحديثات لواجهتك تلقائياً
- خفيف الوزن: حزمة WASM بحجم 3 ميغابايت تقريباً بعد الضغط
PGlite مثالي للنماذج الأولية، التطبيقات المحلية أولاً، الأدوات المدمجة، إضافات المتصفح، وأي سيناريو تريد فيه قوة SQL بدون تعقيد الخوادم.
الخطوة 1: إنشاء مشروع React جديد
ابدأ بإنشاء مشروع React جديد مع Vite و TypeScript:
npm create vite@latest pglite-todo -- --template react-ts
cd pglite-todoثبّت الحزم الأساسية:
npm install @electric-sql/pglite
npm install @electric-sql/pglite-reactحزمة @electric-sql/pglite تحتوي على بناء PostgreSQL WASM، و @electric-sql/pglite-react توفر React hooks للاستعلامات الحية وإدارة قاعدة البيانات.
الخطوة 2: تهيئة قاعدة بيانات PGlite
أنشئ ملفاً جديداً src/db.ts لإعداد نسخة قاعدة البيانات:
// src/db.ts
import { PGlite } from "@electric-sql/pglite";
// استخدام IndexedDB للتخزين الدائم عبر إعادة تحميل الصفحة
const db = new PGlite("idb://todo-app");
export default db;البادئة idb:// تُخبر PGlite بتخزين البيانات في IndexedDB. بدونها، ستوجد البيانات في الذاكرة فقط وتختفي عند التحديث. خيارات التخزين الأخرى تشمل:
memory://— في الذاكرة فقط (ممتاز للاختبارات)idb://database-name— محفوظ في IndexedDB- مسار ملف في Node.js — محفوظ في نظام الملفات
الخطوة 3: إنشاء مخطط قاعدة البيانات
أنشئ ملف ترحيل src/migrate.ts الذي يُعدّ جدول المهام:
// src/migrate.ts
import db from "./db";
export async function runMigrations() {
await db.exec(`
CREATE TABLE IF NOT EXISTS todos (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
completed BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT NOW()
);
`);
}هذا هو DDL معياري في PostgreSQL — PGlite يدعم نفس صيغة SQL التي تستخدمها مع خادم Postgres عادي. عبارة IF NOT EXISTS تضمن أن الترحيل آمن ويمكن تشغيله عند كل بدء للتطبيق.
الخطوة 4: بناء موفّر PGlite
غلّف تطبيقك بموفّر يُهيّئ قاعدة البيانات قبل عرض المكونات الفرعية. أنشئ src/PGliteProvider.tsx:
// src/PGliteProvider.tsx
import { PGliteProvider } from "@electric-sql/pglite-react";
import { useEffect, useState, type ReactNode } from "react";
import db from "./db";
import { runMigrations } from "./migrate";
export function DatabaseProvider({ children }: { children: ReactNode }) {
const [ready, setReady] = useState(false);
useEffect(() => {
runMigrations().then(() => setReady(true));
}, []);
if (!ready) {
return <div className="loading">جارٍ تهيئة قاعدة البيانات...</div>;
}
return <PGliteProvider db={db}>{children}</PGliteProvider>;
}الخطوة 5: تنفيذ قائمة المهام بالاستعلامات الحية
الآن أنشئ مكوّن المهام الرئيسي في src/TodoApp.tsx. هنا يتألق PGlite حقاً — خطاف useLiveQuery يُعيد عرض مكوّنك تلقائياً كلما تغيّرت البيانات:
// src/TodoApp.tsx
import { useLiveQuery, usePGlite } from "@electric-sql/pglite-react";
import { useState } from "react";
interface Todo {
id: number;
title: string;
completed: boolean;
created_at: string;
}
export function TodoApp() {
const db = usePGlite();
const [newTitle, setNewTitle] = useState("");
// استعلام حي — يُحدّث تلقائياً عند تغيّر البيانات
const todos = useLiveQuery<Todo>(
"SELECT * FROM todos ORDER BY created_at DESC"
);
const addTodo = async () => {
if (!newTitle.trim()) return;
await db.exec("INSERT INTO todos (title) VALUES ($1)", [newTitle.trim()]);
setNewTitle("");
};
const toggleTodo = async (id: number, completed: boolean) => {
await db.exec("UPDATE todos SET completed = $1 WHERE id = $2", [
!completed,
id,
]);
};
const deleteTodo = async (id: number) => {
await db.exec("DELETE FROM todos WHERE id = $1", [id]);
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter") addTodo();
};
return (
<div className="todo-app">
<h1>تطبيق مهام PGlite</h1>
<p className="subtitle">
مدعوم بـ PostgreSQL يعمل في متصفحك عبر WebAssembly
</p>
<div className="input-row">
<input
type="text"
value={newTitle}
onChange={(e) => setNewTitle(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="ما الذي يجب إنجازه؟"
/>
<button onClick={addTodo}>إضافة</button>
</div>
<ul className="todo-list">
{todos?.rows.map((todo) => (
<li key={todo.id} className={todo.completed ? "completed" : ""}>
<label>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id, todo.completed)}
/>
<span>{todo.title}</span>
</label>
<button className="delete" onClick={() => deleteTodo(todo.id)}>
حذف
</button>
</li>
))}
</ul>
{todos?.rows.length === 0 && (
<p className="empty">لا توجد مهام بعد. أضف واحدة أعلاه!</p>
)}
</div>
);
}لاحظ أنك لا تُحدّث نتائج الاستعلام يدوياً أبداً. عندما تستدعي db.exec لإدراج أو تحديث أو حذف صف، يكتشف خطاف useLiveQuery التغيير ويُعيد عرض المكوّن تلقائياً. هذا مشابه لكيفية عمل الاشتراكات الفورية في Supabase أو Firebase، لكن كل شيء يحدث محلياً بدون أي تأخير شبكي.
الخطوة 6: ربط التطبيق
حدّث src/App.tsx لاستخدام موفّر قاعدة البيانات ومكوّن المهام:
// src/App.tsx
import { DatabaseProvider } from "./PGliteProvider";
import { TodoApp } from "./TodoApp";
import "./App.css";
function App() {
return (
<DatabaseProvider>
<TodoApp />
</DatabaseProvider>
);
}
export default App;الخطوة 7: إضافة التنسيق
استبدل محتويات src/App.css بتنسيق نظيف لتطبيق المهام:
/* src/App.css */
:root {
font-family: system-ui, -apple-system, sans-serif;
line-height: 1.6;
color: #1a1a2e;
background: #f0f0f5;
}
.loading {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-size: 1.2rem;
color: #666;
}
.todo-app {
max-width: 600px;
margin: 2rem auto;
padding: 2rem;
background: white;
border-radius: 12px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
}
h1 {
margin: 0 0 0.25rem;
font-size: 1.8rem;
}
.subtitle {
margin: 0 0 1.5rem;
color: #888;
font-size: 0.9rem;
}
.input-row {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
}
.input-row input {
flex: 1;
padding: 0.75rem 1rem;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 1rem;
transition: border-color 0.2s;
}
.input-row input:focus {
outline: none;
border-color: #6c63ff;
}
.input-row button {
padding: 0.75rem 1.5rem;
background: #6c63ff;
color: white;
border: none;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
transition: background 0.2s;
}
.input-row button:hover {
background: #5a52d5;
}
.todo-list {
list-style: none;
padding: 0;
margin: 0;
}
.todo-list li {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 0;
border-bottom: 1px solid #f0f0f0;
}
.todo-list li label {
display: flex;
align-items: center;
gap: 0.75rem;
cursor: pointer;
flex: 1;
}
.todo-list li.completed span {
text-decoration: line-through;
color: #aaa;
}
.delete {
background: none;
border: none;
color: #e74c3c;
cursor: pointer;
font-size: 0.85rem;
padding: 0.25rem 0.5rem;
border-radius: 4px;
}
.delete:hover {
background: #ffeaea;
}
.empty {
text-align: center;
color: #999;
padding: 2rem 0;
}الخطوة 8: تشغيل واختبار التطبيق
شغّل خادم التطوير:
npm run devافتح متصفحك على http://localhost:5173. يجب أن ترى تطبيق المهام. جرّب التالي:
- أضف بعض المهام — تظهر فوراً في القائمة
- ضع علامة وأزلها عن المهام — تتحدث الواجهة في الوقت الفعلي
- أعد تحميل الصفحة — مهامك محفوظة بفضل تخزين IndexedDB
- افتح أدوات المطور واذهب إلى Application ثم IndexedDB — يمكنك رؤية ملفات قاعدة بيانات PGlite
الخطوة 9: استعلامات وميزات متقدمة
من أعظم نقاط قوة PGlite التوافق الكامل مع PostgreSQL. دعنا نضيف بعض الميزات المتقدمة لإظهار هذه القوة.
البحث النصي الكامل
أضف شريط بحث يستخدم بحث النص الكامل المدمج في PostgreSQL عبر tsvector:
// أضف وظيفة بحث إلى TodoApp
const searchTodos = async (query: string) => {
if (!query.trim()) return;
const result = await db.query<Todo>(
`SELECT * FROM todos
WHERE to_tsvector('english', title) @@ plainto_tsquery('english', $1)
ORDER BY created_at DESC`,
[query]
);
return result.rows;
};إحصائيات مجمّعة
استعلم عن إحصائيات مهامك باستخدام تجميعات SQL القياسية:
const stats = useLiveQuery<{
total: number;
completed: number;
pending: number;
}>(`
SELECT
COUNT(*) as total,
COUNT(*) FILTER (WHERE completed = true) as completed,
COUNT(*) FILTER (WHERE completed = false) as pending
FROM todos
`);عمليات JSON
يدعم PGlite معاملات JSONB في PostgreSQL، مما يُمكّن تخزين المستندات المعقدة:
// تخزين البيانات الوصفية كـ JSONB
await db.exec(`
ALTER TABLE todos ADD COLUMN IF NOT EXISTS
metadata JSONB DEFAULT '{}'::jsonb
`);
// الاستعلام بمعاملات JSON
await db.exec(
`UPDATE todos SET metadata = metadata || $1::jsonb WHERE id = $2`,
[JSON.stringify({ priority: "high", tags: ["work"] }), todoId]
);الخطوة 10: استخدام PGlite في Node.js
PGlite لا يقتصر على المتصفح. يمكنك استخدام نفس الواجهة البرمجية في Node.js لأدوات سطر الأوامر والسكربتات والاختبارات والدوال بدون خادم:
// node-example.ts
import { PGlite } from "@electric-sql/pglite";
async function main() {
// في Node.js، استخدم مسار ملف للتخزين الدائم
const db = new PGlite("./my-local-db");
await db.exec(`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE
)
`);
await db.exec(
"INSERT INTO users (name, email) VALUES ($1, $2) ON CONFLICT DO NOTHING",
["Alice", "alice@example.com"]
);
const result = await db.query("SELECT * FROM users");
console.log(result.rows);
await db.close();
}
main();شغّله بـ:
npx tsx node-example.tsلا Docker، لا docker-compose، لا تثبيت Postgres — مجرد حزمة npm واحدة.
الخطوة 11: الاختبار مع PGlite
PGlite ممتاز للاختبارات. بدلاً من محاكاة قاعدة البيانات أو تشغيل حاويات Docker في CI، استخدم نسخة PGlite في الذاكرة:
// __tests__/todo.test.ts
import { PGlite } from "@electric-sql/pglite";
import { describe, it, expect, beforeEach } from "vitest";
describe("عمليات المهام", () => {
let db: PGlite;
beforeEach(async () => {
// قاعدة بيانات جديدة في الذاكرة لكل اختبار
db = new PGlite();
await db.exec(`
CREATE TABLE todos (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
completed BOOLEAN DEFAULT false
)
`);
});
it("يجب إدراج مهمة", async () => {
await db.exec("INSERT INTO todos (title) VALUES ($1)", ["شراء حليب"]);
const result = await db.query("SELECT * FROM todos");
expect(result.rows).toHaveLength(1);
expect(result.rows[0].title).toBe("شراء حليب");
});
it("يجب تبديل حالة الإكمال", async () => {
await db.exec("INSERT INTO todos (title) VALUES ($1)", ["تمارين"]);
await db.exec("UPDATE todos SET completed = true WHERE id = 1");
const result = await db.query("SELECT completed FROM todos WHERE id = 1");
expect(result.rows[0].completed).toBe(true);
});
});هذا النهج يمنحك سلوك PostgreSQL حقيقي في الاختبارات — بدون محاكاة، بدون Docker، والاختبارات تعمل في أجزاء من الثانية.
استكشاف الأخطاء
فشل تحميل WASM
إذا فشل تحميل ملف WASM الثنائي، تأكد من أن مُجمّع الحزم لديك مُعدّ للتعامل مع ملفات .wasm. مع Vite، يعمل هذا تلقائياً. لـ Webpack، قد تحتاج:
// webpack.config.js
module.exports = {
experiments: {
asyncWebAssembly: true,
},
};البيانات لا تُحفظ
تأكد من استخدام البادئة idb:// في المُنشئ. بدونها، يستخدم PGlite التخزين في الذاكرة افتراضياً:
// دائم
const db = new PGlite("idb://my-app");
// في الذاكرة فقط (البيانات تُفقد عند التحديث)
const db = new PGlite();حجم الحزمة الكبير
ملف PGlite WASM الثنائي بحجم 3 ميغابايت تقريباً بعد الضغط. لتحسين وقت التحميل الأولي:
- استخدم الاستيراد الديناميكي لتحميل PGlite بشكل كسول
- اعرض مؤشر تحميل أثناء تهيئة WASM
- فكّر في استخدام Service Worker لتخزين ملف WASM مؤقتاً
أخطاء SharedArrayBuffer
بعض ميزات PGlite تتطلب SharedArrayBuffer، الذي يحتاج ترويسات CORS محددة:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
في Vite، أضف هذا لإعداداتك:
// vite.config.ts
export default defineConfig({
plugins: [react()],
server: {
headers: {
"Cross-Origin-Opener-Policy": "same-origin",
"Cross-Origin-Embedder-Policy": "require-corp",
},
},
});متى تستخدم PGlite
PGlite خيار ممتاز لـ:
- النماذج الأولية: تخطّ إعداد قاعدة البيانات وابدأ البناء فوراً
- التطبيقات المحلية أولاً: تطبيقات المهام، تدوين الملاحظات، الأدوات الشخصية
- إضافات المتصفح: قاعدة بيانات مدمجة بقوة SQL كاملة
- الاختبارات: استبدل قواعد بيانات الاختبار المبنية على Docker بنسخ فورية في الذاكرة
- دوال الحافة: شغّل Postgres في بيئات بدون خادم دون اتصالات خارجية
- التطبيقات بدون اتصال: تطبيقات الويب التقدمية التي تحتاج تخزين بيانات منظم
فكّر في خادم قاعدة بيانات تقليدي عندما تحتاج:
- وصول متعدد المستخدمين لبيانات مشتركة
- قواعد بيانات أكبر من بضع مئات ميغابايت
- التحقق من البيانات والتحكم في الوصول من جانب الخادم
- النسخ المتماثل والتوفر العالي
الخطوات التالية
الآن بعد أن أصبح PGlite يعمل في المتصفح، إليك بعض الطرق لتوسيع معرفتك:
- أضف مزامنة ElectricSQL: ادمج PGlite مع ElectricSQL لمزامنة قاعدة بياناتك المحلية مع نسخة Postgres على الخادم
- أنشئ PWA: أضف Service Worker لجعل تطبيقك يعمل بالكامل بدون اتصال
- استخدم إضافات PGlite: يدعم PGlite إضافات مثل
pgvectorللبحث بتشابه المتجهات مباشرة في المتصفح - استكشف دعم التبويبات المتعددة: استخدم تنسيق التبويبات المتعددة في PGlite لمشاركة قاعدة بيانات عبر تبويبات المتصفح
الخلاصة
يمثل PGlite تحولاً جذرياً في كيفية تفكيرنا بقواعد البيانات في تطبيقات الويب. بترجمة PostgreSQL إلى WebAssembly، يجلب قوة SQL الكاملة — الربط والفهارس والبحث النصي الكامل و JSONB وأكثر — مباشرة إلى المتصفح بدون أي بنية تحتية للخادم.
في هذا الدليل، بنيت تطبيق مهام كامل يحفظ البيانات محلياً، ويتفاعل مع التغييرات تلقائياً، ويعمل بالكامل من جانب العميل. كما استكشفت ميزات متقدمة مثل البحث النصي الكامل وعمليات JSON واستراتيجيات الاختبار.
حركة المحلي أولاً تكتسب زخماً، و PGlite في طليعتها. سواء كنت تبني نموذجاً أولياً أو إضافة متصفح أو تطبيقاً يعمل بدون اتصال، يمنحك PGlite قوة قاعدة البيانات التي تحتاجها بدون العبء التشغيلي.
ناقش مشروعك معنا
نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.
دعنا نجد أفضل الحلول لاحتياجاتك.
مقالات ذات صلة

بناء تطبيقات تعاونية محلية أولاً باستخدام Yjs و React
تعلم كيفية بناء تطبيقات تعاونية تعمل في الوقت الفعلي حتى بدون اتصال بالإنترنت باستخدام تقنية CRDTs ومكتبة Yjs مع React. يغطي هذا الدليل مزامنة البيانات بدون تعارضات، والهندسة المعمارية المحلية أولاً، وبناء محرر مستندات مشترك من الصفر.

بناء تطبيق فوري مع Supabase و Next.js 15: الدليل الشامل
تعلّم كيفية بناء تطبيق full-stack فوري باستخدام Supabase و Next.js 15 App Router. يغطي هذا الدليل المصادقة وإعداد قاعدة البيانات و Row Level Security والاشتراكات الفورية.

Capacitor + React — بناء تطبيقات موبايل متعددة المنصات من تطبيقك الويب (2026)
حوّل تطبيق React الخاص بك إلى تطبيق iOS و Android أصلي باستخدام Capacitor. يغطي هذا الدليل العملي الإعداد، الإضافات الأصلية، الكاميرا، التخزين المحلي، النشر على المتاجر، وأفضل ممارسات الإنتاج.