Node.js: SQLite و Prisma
آخر تحديث: 2026-08-26
يعمل تشارلي على تطوير تطبيق لتدوين الملاحظات على أجهزة الكمبيوتر المكتبية يتطلب تخزين البيانات محليًّا، لكنه لا يريد أن يضطر المستخدمون إلى تثبيت خدمة MySQL منفصلة. SQLite هي قاعدة بيانات مدمجة لا تتطلب أي إعدادات، حيث يشكل ملف واحد قاعدة البيانات بأكملها؛ وعند استخدامها مع Prisma ORM، فإنها توفر أمانًا كاملاً للأنواع في عمليات SQL.
1. better-sqlite3: قاعدة بيانات مدمجة مزودة بواجهة برمجة تطبيقات متزامنة
(1) لماذا تختار better-sqlite3؟
توجد العديد من مكتبات SQLite في منظومة Node.js، وتشتهر مكتبة better-sqlite3 بـواجهة برمجة التطبيقات المتزامنة — فهي تُرجع النتيجة فور استدعائها، دون الحاجة إلى انتظار أو عمليات الاستدعاء المرتدة المتداخلة. وتقوم هذه المكتبة بترجمة SQLite إلى لغة C++ على المستوى المنخفض، مما ينتج عنه أداء يفوق بكثير أداء مكتبات الغلاف غير المتزامنة.
| الميزة | الوصف |
|---|---|
| واجهة برمجة التطبيقات (API) للمزامنة | لا «جحيم الاستدعاءات المرتدة»، كود خطي وسهل القراءة |
| لا حاجة إلى أي إعدادات | لا حاجة لتثبيت خدمة قاعدة بيانات — npm install جاهز للاستخدام |
| التخزين في ملف واحد | قاعدة البيانات بأكملها عبارة عن ملف .db واحد |
| دعم المعاملات | المعاملات المتداخلة والبيانات المعدة مسبقًا — كلها مضمنة |
| الأداء العالي | واجهات C++، أسرع بـ 2–3 مرات من node-sqlite3 |
| متعدد المنصات | يمكن تجميعه على أنظمة ويندوز وماك أو إس ولينكس |
(2) التركيب والتوصيلات الأساسية
npm init -y
npm install better-sqlite3
const Database = require('better-sqlite3');
const db = new Database('myapp.db');
db.pragma('journal_mode = WAL');
db.pragma('foreign_keys = ON');
console.log('SQLite Version:', db.prepare('SELECT sqlite_version()').get());
(3) دليل مرجعي سريع للطرق الشائعة
| الطريقة | الغرض | مثال |
|---|---|---|
db.prepare(sql) |
إنشاء عبارة مُجمَّعة مسبقًا | const stmt = db.prepare('SELECT * FROM users WHERE id = ?') |
stmt.run(...params) |
تنفيذ أوامر INSERT/UPDATE/DELETE | stmt.run(1, 'Charlie') |
stmt.get(...params) |
العودة إلى الكائن أحادي السطر | stmt.get(1) |
stmt.all(...params) |
إرجاع مصفوفة تضم جميع الصفوف | stmt.all() |
stmt.values(...params) |
مصفوفة قيم الإرجاع (بدون أسماء مفاتيح) | stmt.values() |
db.exec(sql) |
تنفيذ عدة أوامر SQL | db.exec(schemaSql) |
db.transaction(fn) |
وظيفة إنشاء معاملة | const insertMany = db.transaction((items) => {...}) |
db.pragma(cmd) |
تعيين/الاستعلام عن PRAGMA | db.pragma('journal_mode = WAL') |
▶ مثال: سير عمل CRUD كامل باستخدام better-sqlite3
const Database = require('better-sqlite3');
const db = new Database('notes.db');
db.exec(`
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
body TEXT DEFAULT '',
created_at TEXT DEFAULT (datetime('now'))
)
`);
const insert = db.prepare('INSERT INTO notes (title, body) VALUES (?, ?)');
const result = insert.run('First Note', 'Hello SQLite!');
console.log('Insert a row ID:', result.lastInsertRowid);
const find = db.prepare('SELECT * FROM notes WHERE id = ?');
console.log('Search Results:', find.get(1));
const update = db.prepare('UPDATE notes SET title = ? WHERE id = ?');
update.run('Revised Title', 1);
const remove = db.prepare('DELETE FROM notes WHERE id = ?');
remove.run(1);
const listAll = db.prepare('SELECT * FROM notes ORDER BY created_at DESC');
console.log('All Notes:', listAll.all());
db.close();
(4) الاستخدام في المعاملات
const insertMany = db.transaction((notes) => {
for (const n of notes) {
insert.run(n.title, n.body);
}
});
insertMany([
{ title: 'Notes A', body: 'Content A' },
{ title: 'Notes B', body: 'Content B' },
]);
2. مراجعة أساسيات جمل SQL
(1) العمليات الأساسية الأربع: CRUD
| العملية | SQL | الكلمة الرئيسية |
|---|---|---|
| إنشاء | إدراج | INSERT INTO table (col) VALUES (val) |
| قراءة | اختيار | SELECT col FROM table WHERE cond |
| تحديث | تحديث | UPDATE table SET col=val WHERE cond |
| حذف | حذف | DELETE FROM table WHERE cond |
(2) جمل الاستعلام الشائعة
SELECT Listed
FROM Table Name
WHERE Conditions
GROUP BY Grouping Column
HAVING Grouping Criteria
ORDER BY Sorted List ASC|DESC
LIMIT Quantity OFFSET offset
(3) استعلامات الانضمام
db.exec(`
CREATE TABLE IF NOT EXISTS authors (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
)
`);
db.exec(`
CREATE TABLE IF NOT EXISTS books (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
author_id INTEGER REFERENCES authors(id)
)
`);
const joinQuery = db.prepare(`
SELECT books.title, authors.name AS author
FROM books
JOIN authors ON books.author_id = authors.id
`);
3. تثبيت برنامج Prisma وتهيئته
(1) ما هو Prisma؟
Prisma هو نموذج ORM من الجيل التالي يعمل مع Node.js وTypeScript. ويتمثل سير العمل الأساسي له فيما يلي:
flowchart LR
A["schema.prisma"] -->|"prisma migrate dev"| B["Migration SQL"]
A -->|"prisma generate"| C["Prisma Client"]
C -->|"Type-Safe Queries"| D[("Database")]
B --> D
- schema.prisma: التعريف التصريحي لنماذج البيانات
- Prisma Migrate: يقوم تلقائيًا بإنشاء وتنفيذ أوامر SQL الخاصة بالترحيل
- Prisma Client: عميل استعلام يتم إنشاؤه تلقائيًا ويتميز بالأمان من حيث الأنواع
(2) بدء المشروع
mkdir prisma-notes && cd prisma-notes
npm init -y
npm install prisma --save-dev
npm install @prisma/クライアント
npx prisma init --datasource-provider sqlite
تم إنشاؤها بعد التهيئة:
prisma-notes/
├── prisma/
│ └── schema.prisma
├── .env
└── package.json
.env محتويات الملف:
DATABASE_URL="file:./dev.db"
(3) دليل مرجعي سريع لـ Prisma Command
| الأمر | الغرض |
|---|---|
npx prisma init |
تهيئة مشروع Prisma |
npx prisma migrate dev |
إنشاء عملية ترحيل للتطوير وتطبيقها |
npx prisma migrate deploy |
ترحيل التطبيقات إلى بيئة الإنتاج |
npx prisma generate |
إنشاء عميل Prisma |
npx prisma studio |
فتح واجهة الإدارة المرئية |
npx prisma db push |
تطبيق المخطط مباشرةً خلال مرحلة النموذج الأولي (دون إنشاء ملفات الترحيل) |
npx prisma db seed |
تشغيل البرنامج النصي لبيانات البذور |
4. schema.prisma: Defining the Model
(1) الهيكل الأساسي
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Note {
id Int @id @default(autoincrement())
title String
body String @default("")
pinned Boolean @default(false)
tags String @default("")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
(2) مرجع سريع لأنواع الحقول
| نوع بريزما | تخطيط SQLite | الوصف |
|---|---|---|
String |
نص | سلسلة |
Int |
INTEGER | عدد صحيح 32 بت |
BigInt |
INTEGER | عدد صحيح 64 بت |
Float |
حقيقي | عدد عائم |
Boolean |
عدد صحيح | 0 أو 1 |
DateTime |
نص | سلسلة ISO 8601 |
Json |
نص | سلسلة JSON |
Bytes |
BLOB | بيانات ثنائية |
Decimal |
نص | أرقام عشرية عالية الدقة |
ملاحظة: يختلف نظام أنواع البيانات في SQLite عن نظيره في PostgreSQL؛ وتقوم Prisma بإجراء التكييف الأساسي. عند التبديل إلى
provider، سيتم تعديل تعيينات أنواع الحقول تلقائيًا.
(3) الخصائص والمُعدِّلات
| المُعدِّل | الغرض | مثال |
|---|---|---|
@id |
المفتاح الأساسي | id Int @id |
@default |
القيمة الافتراضية | @default(autoincrement()) / @default(now()) / @default("active") |
@unique |
قيد فريد | email String @unique |
@relation |
تعريف العلاقة | @relation(fields: [authorId], references: [id]) |
@map / @@map |
تعيين أسماء الأعمدة/الجداول | @map("created_at") |
@@unique |
مركب فريد | @@unique([firstName, lastName]) |
@@index |
المؤشر المركب | @@index([categoryId, createdAt]) |
? |
حقل اختياري | bio String? |
(4) تعريفات العلاقات
model User {
id Int @id @default(autoincrement())
email String @unique
name String
notes Note[]
}
model Note {
id Int @id @default(autoincrement())
title String
body String @default("")
authorId Int
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
}
▶ مثال: تعريف علاقات متعددة النماذج
// prisma/schema.prisma — العلاقات بين نماذج User و Note و Tag
model User {
id Int @id @default(autoincrement())
email String @unique
name String
notes Note[]
createdAt DateTime @default(now())
}
model Note {
id Int @id @default(autoincrement())
title String
body String @default("")
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
tags NoteTag[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Tag {
id Int @id @default(autoincrement())
name String @unique
notes NoteTag[]
}
model NoteTag {
noteId Int
tagId Int
note Note @relation(fields: [noteId], references: [id], onDelete: Cascade)
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
@@id([noteId, tagId])
@@index([tagId])
}
يحدد هذا المثال ثلاثة نماذج مع نوعين من العلاقات:
- User → Note: واحد-إلى-متعدد، حذف متتالي
- Note ↔ Tag: متعدد-إلى-متعدد عبر جدول الربط
NoteTag، باستخدام مفتاح أساسي مركب@@idوفهرس@@index
5. عملية الترحيل باستخدام Prisma Migrate
(1) إنشاء عملية ترحيل وتطبيقها
npx prisma migrate dev --name init
بعد التنفيذ:
prisma/
├── schema.prisma
└── migrations/
└── 20260703_init/
└── migration.sql
تم إنشاؤه migration.sql:
CREATE TABLE "Note" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"title" TEXT NOT NULL,
"body" TEXT NOT NULL DEFAULT '',
"pinned" BOOLEAN NOT NULL DEFAULT false,
"tags" TEXT NOT NULL DEFAULT '',
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
(2) سير عمل الهجرة
Development Phase: schema Edit → npx prisma migrate dev --name Description
Testing Phase: npx prisma migrate deploy(App Only,Do not create a new migration)
Prototyping Phase: npx prisma db push(Skip migrating files,Rapid Iteration)
Reset Data: npx prisma migrate reset(Clear the database and replay all migrations)
(3) نشر الإنتاج
npx prisma migrate deploy
migrate deployيُشغّل عمليات الترحيل التي لم يتم تطبيقها فقط؛ ولا ينشئ عمليات ترحيل جديدة ولا يعيد تعيين البيانات — وهو خيار مثالي لمسارات CI/CD.
6. عمليات CRUD في عميل Prisma
(1) تهيئة العميل
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
async function main() {
// CRUD Instructions are written here
}
main()
.catch(console.error)
.finally(() => prisma.$disconnect());
(2) إنشاء — إنشاء
const note = await prisma.note.create({
data: {
title: 'Study Prisma',
body: 'Prisma Making Database Operations Type-Safe',
pinned: true,
},
});
const notes = await prisma.note.createMany({
data: [
{ title: 'Notes A', body: 'Content A' },
{ title: 'Notes B', body: 'Content B', pinned: true },
],
});
(3) قراءة — استعلام
const one = await prisma.note.findUnique({ where: { id: 1 } });
const first = await prisma.note.findFirst({
where: { pinned: true },
orderBy: { createdAt: 'desc' },
});
const all = await prisma.note.findMany();
const filtered = await prisma.note.findMany({
where: {
pinned: true,
title: { contains: 'Prisma' },
},
});
(4) تحديث — تحديث
const updated = await prisma.note.update({
where: { id: 1 },
data: { title: 'Updated Title', pinned: false },
});
const count = await prisma.note.updateMany({
where: { pinned: false },
data: { tags: 'archived' },
});
(5) حذف — حذف
const deleted = await prisma.note.delete({ where: { id: 1 } });
const deleteCount = await prisma.note.deleteMany({
where: { pinned: false },
});
(6) قائمة مرشحات الاستعلام
| الفلتر | المعنى | مثال |
|---|---|---|
equals |
يساوي | { title: { equals: 'Hello' } } |
not |
لا يساوي | { id: { not: 1 } } |
contains |
تضمين | { title: { contains: 'Prisma' } } |
startsWith |
البادئة | { title: { startsWith: 'Learn' } } |
endsWith |
لاحقة | { email: { endsWith: '@test.com' } } |
in |
في القائمة | { id: { in: [1, 2, 3] } } |
notIn |
غير موجود في القائمة | { id: { notIn: [4, 5] } } |
lt / lte |
أقل من / أقل من أو يساوي | { id: { lte: 10 } } |
gt / gte |
أكبر من / أكبر من أو يساوي | { id: { gte: 5 } } |
AND |
و | { AND: [{ pinned: true }, { title: { contains: 'A' } }] } |
OR |
أو | { OR: [{ pinned: true }, { pinned: false }] } |
NOT |
لا | { NOT: { title: 'Hello' } } |
▶ مثال: تضمين البيانات المرتبطة والتصفية
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
async function queryNotes() {
// البحث عن الملاحظات المنشورة مع المؤلف والوسوم
const notes = await prisma.note.findMany({
where: {
published: true,
title: { contains: 'Prisma' },
},
include: {
author: {
select: { id: true, name: true, email: true },
},
tags: {
include: {
tag: { select: { id: true, name: true } },
},
},
},
orderBy: { createdAt: 'desc' },
take: 10,
});
console.log(`تم العثور على ${notes.length} ملاحظة`);
for (const note of notes) {
const tagNames = note.tags.map((nt) => nt.tag.name).join(', ');
console.log(`${note.title} — ${note.author.name} [${tagNames}]`);
}
}
queryNotes()
.catch(console.error)
.finally(() => prisma.$disconnect());
يستخدم هذا المثال include لتحميل author و tags المرتبطة مسبقًا، و select لتحديد الحقول المطلوبة فقط. تتضمن النتيجة بيانات متداخلة بدون استعلامات إضافية.
7. الفرز وتقسيم الصفحات
(1) الفرز
const sorted = await prisma.note.findMany({
orderBy: [
{ pinned: 'desc' },
{ createdAt: 'desc' },
],
});
(2) ترقيم الصفحات
const PAGE_SIZE = 10;
const page1 = await prisma.note.findMany({
skip: 0,
take: PAGE_SIZE,
orderBy: { createdAt: 'desc' },
});
const page2 = await prisma.note.findMany({
skip: PAGE_SIZE,
take: PAGE_SIZE,
orderBy: { createdAt: 'desc' },
});
(3) ترقيم الصفحات باستخدام المؤشر (موصى به لمجموعات البيانات الكبيرة)
const first = await prisma.note.findMany({
take: 10,
orderBy: { id: 'asc' },
});
const cursor = first[first.length - 1].id;
const next = await prisma.note.findMany({
take: 10,
skip: 1,
cursor: { id: cursor },
orderBy: { id: 'asc' },
});
▶ مثال: البحث وتقسيم الصفحات بالمؤشر
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
async function searchNotes(searchTerm, cursorId = null) {
const PAGE_SIZE = 5;
const results = await prisma.note.findMany({
take: PAGE_SIZE + 1,
skip: cursorId ? 1 : 0,
cursor: cursorId ? { id: cursorId } : undefined,
where: {
OR: [
{ title: { contains: searchTerm } },
{ body: { contains: searchTerm } },
],
},
include: { author: { select: { name: true } } },
orderBy: { id: 'asc' },
});
const hasMore = results.length > PAGE_SIZE;
if (hasMore) results.pop();
return {
items: results,
nextCursor: hasMore ? results[results.length - 1].id : null,
hasMore,
};
}
async function main() {
let cursor = null;
for (let page = 1; page <= 3; page++) {
const { items, nextCursor, hasMore } = await searchNotes('Prisma', cursor);
console.log(`الصفحة ${page}: ${items.length} نتيجة`);
items.forEach((n) => console.log(` ${n.id}: ${n.title}`));
if (!hasMore) break;
cursor = nextCursor;
}
}
main()
.catch(console.error)
.finally(() => prisma.$disconnect());
يجمع بين البحث النصي الكامل في حقلي title و body مع التقسيم إلى صفحات باستخدام المؤشر. يتم جلب سجل إضافي واحد لتحديد ما إذا كانت هناك صفحة تالية، ثم حذفه قبل الإرجاع.
8. مقارنة بين قواعد البيانات
(1) مقارنة بين SQLite و MySQL و PostgreSQL و MongoDB
| البعد | SQLite | MySQL | PostgreSQL | MongoDB |
|---|---|---|---|---|
| النوع | المدمج | العميل-الخادم | العميل-الخادم | القائم على المستندات |
| التثبيت | لا يتطلب تثبيتًا (مضمن في حزمة npm) | يتطلب تثبيت خدمة | يتطلب تثبيت خدمة | يتطلب تثبيت خدمة |
| عمليات الكتابة المتزامنة | كاتب واحد | كتابة متعددة | كتابة متعددة | كتابة متعددة |
| حجم البيانات | صغير إلى متوسط (بمقياس الجيجابايت) | كبير (بمقياس التيرابايت) | كبير (بمقياس التيرابايت) | كبير (بمقياس التيرابايت) |
| حالات الاستخدام | تطبيقات سطح المكتب / النماذج الأولية / الاختبار | تطبيقات الويب / المشاريع متوسطة الحجم | الاستعلامات المعقدة / البيانات الجغرافية المكانية | المخطط المرن / السجلات |
| دعم JSON | محدود (امتدادات JSON-1) | مدعوم | JSONB أصلي | وثائق أصلية |
| البحث عن النص الكامل | ملحق FTS5 | فهرس النص الكامل | tsvector | فهرس النص |
| المعاملات | ACID الكامل | ACID الكامل | ACID الكامل | 4.0+ المعاملات متعددة المستندات |
| الترخيص | الملكية العامة | GPL / تجاري | PostgreSQL | SSPL |
(2) مقارنة بين أطر عمل إدارة السمعة (ORM)
| البعد | Mongoose | Prisma | Sequelize | TypeORM |
|---|---|---|---|---|
| اللغة | جافا سكريبت | يُفضل استخدام تايب سكريبت | جافا سكريبت | يُفضل استخدام تايب سكريبت |
| قاعدة البيانات | MongoDB فقط | SQLite/MySQL/PostgreSQL/MongoDB | MySQL/PostgreSQL/SQLite/MSSQL | MySQL/PostgreSQL/SQLite/MSSQL |
| تعريف المخطط | كائن JS | .prisma ملف تعريفي |
تعريف نموذج JS | فئة الزخرفة / الكيان |
| أمان الأنواع | ضعيف (يدوي) | قوي (يتم إنشاؤه تلقائيًا) | ضعيف | متوسط (أنواع الزخرفة) |
| أداة الترحيل | غير مدمجة | prisma migrate |
sequelize-cli |
مدمجة |
| طريقة الاستعلام | واجهة برمجة التطبيقات المتسلسلة | الكائن المتسلسل | SQL المتسلسل / الأصلي | QueryBuilder / الأصلي |
| مشكلة N+1 | تحتاج إلى تعبئة | التضمين التلقائي | تحتاج إلى التضمين الفوري/المؤجل | تحتاج إلى علاقات |
| حجم المجتمع | كبير | ينمو بسرعة | كبير | كبير |
| المشاريع المناسبة | مشاريع MongoDB | TypeScript متكامل | Node.js التقليدي | منظومة NestJS |
9. مثال شامل: طبقة بيانات إدارة الملاحظات
قم بإنشاء طبقة بيانات CRUD كاملة لإدارة الملاحظات باستخدام Prisma وSQLite.
▶ مثال: إدارة الملاحظات باستخدام Prisma وSQLite
الخطوة 1 — تعريف المخطط
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Note {
id Int @id @default(autoincrement())
title String
body String @default("")
pinned Boolean @default(false)
tags String @default("")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("notes")
}
الخطوة 2 — الترحيل
npx prisma migrate dev --name notes_init
الخطوة 3 — طبقة الوصول إلى البيانات
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
async function createNote(data) {
return prisma.note.create({ data });
}
async function getNoteById(id) {
return prisma.note.findUnique({ where: { id } });
}
async function updateNote(id, data) {
return prisma.note.update({ where: { id }, data });
}
async function deleteNote(id) {
return prisma.note.delete({ where: { id } });
}
async function listNotes({ page = 1, pageSize = 10, pinned, keyword } = {}) {
const where = {};
if (pinned !== undefined) where.pinned = pinned;
if (keyword) where.title = { contains: keyword };
const [items, total] = await Promise.all([
prisma.note.findMany({
where,
orderBy: [{ pinned: 'desc' }, { createdAt: 'desc' }],
skip: (page - 1) * pageSize,
take: pageSize,
}),
prisma.note.count({ where }),
]);
return { items, total, page, pageSize, totalPages: Math.ceil(total / pageSize) };
}
async function togglePin(id) {
const note = await prisma.note.findUnique({ where: { id } });
if (!note) throw new Error('The note does not exist.');
return prisma.note.update({
where: { id },
data: { pinned: !note.pinned },
});
}
module.exports = {
createNote,
getNoteById,
updateNote,
deleteNote,
listNotes,
togglePin,
};
الخطوة 4 — مثال على الاستخدام
const db = require('./note-service');
async function main() {
const n1 = await db.createNote({ title: 'Study Prisma', body: 'Type Safety ORM', pinned: true });
const n2 = await db.createNote({ title: 'SQLite Key Points', body: 'Zero-Configuration Embedded Database' });
const n3 = await db.createNote({ title: 'Prisma Migration', body: 'migrate dev Driver' });
console.log('Single-Record Query:', await db.getNoteById(n1.id));
await db.updateNote(n2.id, { body: 'What's New' });
await db.togglePin(n3.id);
const result = await db.listNotes({ page: 1, pageSize: 10, keyword: 'Prisma' });
console.log('Search Results:', result);
await db.deleteNote(n2.id);
const all = await db.listNotes({ page: 1, pageSize: 10 });
console.log('Remaining Notes:', all);
}
main()
.catch(console.error)
.finally(() => require('@prisma/client').PrismaClient &&
require('./node_modules/.prisma/client').$disconnect?.());
تنفيذ:
node index.js
الخطوة 5 — الإدارة المرئية
npx prisma studio
افتح http://localhost:5555 في متصفحك لعرض البيانات وتحريرها بصريًّا.
10. ملخص هذا الدرس
- SQLite هي قاعدة بيانات مدمجة لا تتطلب أي إعدادات؛
better-sqlite3توفر واجهة برمجة تطبيقات (API) للتزامن عالية الأداء - يستخدم Prisma
schema.prismaلتعريف النماذج بطريقة إعلانية، حيث يقوم تلقائيًا بإنشاء عمليات الترحيل وعملاء آمنين من حيث الأنواع prisma migrate devلتطوير برامج التشغيل والترحيل؛prisma migrate deployللنشر في بيئة الإنتاج- يدعم برنامج Prisma Client
findManyوcreateوupdateوdelete، بالإضافة إلى مجموعة واسعة من خيارات التصفية والفرز وتقسيم الصفحات - اختيار قاعدة البيانات: اختر SQLite لتطبيقات سطح المكتب/النماذج الأولية؛ واختر MySQL أو PostgreSQL لتطبيقات الويب؛ واختر MongoDB للمخططات المرنة
- اختيار أداة ORM: استخدم Mongoose مع MongoDB، وPrisma لتطبيقات TypeScript كاملة المكونات، وTypeORM مع NestJS
❓ أسئلة شائعة
defineModel. توفر Prisma أمانًا أفضل من حيث الأنواع، في حين تتمتع Sequelize بنظام بيئي أكثر نضجًا.migrate وdb push؟migrate بإنشاء ملفات الترحيل، وهي مناسبة للتعاون الجماعي ونشر التطبيقات في بيئة الإنتاج؛ أما db push فيقوم بمزامنة المخطط مباشرةً مع قاعدة البيانات، وهو مناسب لإنشاء النماذج الأولية.schema.prisma إلى postgresql، وقم بتحديث سلسلة الاتصال DATABASE_URL، ثم قم بتشغيل prisma migrate reset لإعادة إنشاء قاعدة البيانات.INCLUDE لتحميل البيانات ذات الصلة مسبقًا، أو استخدم SELECT لاختيار الحقول بدقة، لتجنب الاستعلام عن السجلات ذات الصلة واحدًا تلو الآخر في حلقة.- س: هل يدعم SQLite التزامن؟ ج: يدعم عمليات قراءة متعددة وعملية كتابة واحدة؛ وفي وضع WAL، يمكن إجراء عمليات القراءة بشكل متزامن، لكن عمليات الكتابة تظل متسلسلة. بالنسبة لسيناريوهات الكتابة التي تتطلب مستوى عالٍ من التزامن، ينبغي عليك التحول إلى MySQL أو PostgreSQL.
- س: أيهما أفضل، Prisma أم Sequelize؟ ج: يوفر Prisma أمانًا أقوى في أنواع البيانات، وتجربة ترحيل أفضل، واستعلامات خالية من مشاكل N+1؛ أما Sequelize فيتمتع بنظام بيئي أكثر نضجًا ويدعم عددًا أكبر من لهجات قواعد البيانات. نوصي باستخدام Prisma للمشاريع الجديدة.
- س: لماذا تعمل مكتبة better-sqlite3 بشكل متزامن؟ ج: إن SQLite نفسها مكتبة تعمل ضمن العملية نفسها، ولا تستغرق عمليات الإدخال/الإخراج سوى ميكروثوانٍ؛ لذا فإن استخدام العمليات غير المتزامنة من شأنه أن يزيد في الواقع من العبء على حلقة الأحداث. وتقوم واجهة برمجة التطبيقات المتزامنة بإلغاء عمليات الاستدعاء المتداخلة، مما يؤدي إلى كود أكثر نظافة.
- س: ما هو الحجم الأقصى لقاعدة بيانات SQLite؟ ج: الحد النظري هو 281 تيرابايت، ولكن في الواقع العملي، يخضع ذلك للحد الأقصى لحجم الملف الواحد الذي يفرضه نظام التشغيل؛ وفي الحالات العادية، لا تشكل قواعد البيانات التي يبلغ حجمها عدة عشرات من الجيجابايت أي مشكلة على الإطلاق.
- س: متى ينبغي عليك الترحيل من SQLite إلى PostgreSQL؟ ج: ينبغي عليك الترحيل عندما تحتاج إلى عمليات كتابة متزامنة عبر عمليات متعددة، أو استعلامات JSONB أصلية، أو البحث عن النص الكامل، أو البيانات الجغرافية المكانية، أو سياسات الأمان على مستوى الصفوف، أو عندما تتجاوز سعة التخزين لجهاز واحد.
- س: هل يدعم Prisma قاعدة بيانات MongoDB؟ ج: يدعم الإصدار 2.0 والإصدارات الأحدث قاعدة بيانات MongoDB، لكن مجموعة الميزات المتوفرة ليست شاملة بقدر تلك المتوفرة لقواعد البيانات العلائقية؛ وبالنسبة لعمليات التجميع المعقدة، نوصي باستخدام Mongoose.
- س: كيف ينبغي إدارة مثيلات Prisma Client في بيئة الإنتاج؟ ج: استخدم مثيلًا فريدًا عالميًّا لتجنب إنشاء مثيل جديد مع كل طلب؛ وفي بيئات إعادة التحميل السريع مثل Next.js، استخدم التخزين المؤقت
globalThisلمنع تسربات الاتصال.
📖 ملخص
- 1 better-sqlite3: المفاهيم الأساسية واستخدام واجهة برمجة التطبيقات المتزامنة (API) لقواعد البيانات المدمجة
- 2 المفاهيم الأساسية واستخدامات جمل SQL: مراجعة للأساسيات
- 3 مفاهيم أساسية واستخدامات تثبيت Prisma وتهيئته
- 4 schema.prisma: تعريف المفاهيم الأساسية واستخدام النموذج
- 5 مفاهيم أساسية واستخدامات أداة Prisma Migrate
- 6 مفاهيم أساسية واستخدامات عمليات CRUD في Prisma Client
- 7 مفاهيم أساسية واستخدامات الفرز وتقسيم الصفحات
- 8 مفاهيم وأساليب أساسية لمقارنة قواعد البيانات واختيارها
📝 تمارين
- استخدم
better-sqlite3لإنشاء جدولusers، وقم بتنفيذ عمليات الإدراج والاستعلام عبر البريد الإلكتروني والتحديث والحذف، واستخدم المعاملات لضمان الترابطية. - قم بتهيئة مشروع Prisma، وحدد نموذجين —
UserوPost(علاقة «واحد إلى عدة») — وبعد تشغيل عمليات الترحيل، استخدم Prisma Client لإنشاء مستخدمين، ونشر مقالات، والاستعلام عن المستخدمين وجميع مقالاتهم. - استنادًا إلى مثال إدارة الملاحظات، أضف النموذج
Categoryلتمكين تصفية الملاحظات حسب الفئة، ودعم التصفية حسب معرّف الفئة فيlistNotes. - قارن بين تنفيذ نفس مجموعة عمليات CRUD باستخدام لغة SQL الأصلية
better-sqlite3وعميل Prisma، مع ملاحظة الاختلافات في عدد الأسطر وسهولة القراءة. - اكتب برنامجًا نصيًّا باستخدام
prisma.note.findManyلتنفيذ البحث عن الكلمات المفتاحية، وإعطاء الأولوية للنتائج الأعلى، وتقسيم الصفحات، ومقارنة الفروق في الأداء بين تقسيم الصفحات بالإزاحة وتقسيم الصفحات بالمؤشر عند معالجة 10,000 سجل.