تخطَّ إلى المحتوى

قاعدة بيانات Firestore: الإضافة والقراءة والتحديث الفوري

الدرس 3 من 9 · Firebase

Cloud Firestore هي قاعدة بيانات Firebase: تخزن بيانات تطبيقك على خوادم Google، ويقرؤها ويكتبها تطبيقك مباشرة دون أن تكتب أي كود خادم. وأجمل ميزاتها التحديث الفوري: إذا غيّر مستخدم شيئاً، تظهر النتيجة على شاشات كل المستخدمين الآخرين خلال لحظات. في هذا الدرس ستبني قاعدة بيانات طلاب، وتجرب كل العمليات الأساسية.

Firestore قاعدة بيانات NoSQL مثل MongoDB، وتنظم البيانات هكذا:

  • المجموعة (Collection): مثل students، تحتوي مستندات.
  • المستند (Document): بيانات طالب واحد بحقول وقيم، وله معرّف فريد.
students (مجموعة)
├── omar (مستند) → { name: 'عمر', age: 15, city: 'الإسكندرية', score: 78 }
├── mona (مستند) → { name: 'منى', age: 13, city: 'طنطا', score: 85 }
└── ...

تخيّلها مثل خزانة ملفات: كل درج مجموعة، وكل ملف داخله مستند له رقم على غلافه.

ولإنشاء قاعدة البيانات: من Firebase Console اختر Firestore Database ثم Create database. وكل الأمثلة التالية تستخدم db التي جهزناها في ملف firebase.js في الدرس السابق.

addDoc: يضيف مستنداً بمعرّف تلقائي:

import { collection, addDoc } from 'firebase/firestore';
const ref = await addDoc(collection(db, 'students'), {
name: 'سارة', age: 14, city: 'القاهرة', score: 92
});
console.log(ref.id);

ref.id نص عشوائي فريد من 20 حرفاً يولّده Firestore، مثل Xk9p2LmQr7vT4wZa1bCd.

setDoc: تختار المعرّف بنفسك:

import { doc, setDoc } from 'firebase/firestore';
await setDoc(doc(db, 'students', 'omar'), {
name: 'عمر', age: 15, city: 'الإسكندرية', score: 78
});

doc(db, 'students', 'omar') تشير إلى المستند omar داخل مجموعة students. ولاحظ await: كل عمليات Firestore غير متزامنة، لأنها تتصل بالإنترنت، تماماً كما تعلمت عن async و await في JavaScript.

import { getDoc } from 'firebase/firestore';
const snap = await getDoc(doc(db, 'students', 'omar'));
console.log(snap.exists(), snap.id, snap.data());
الناتج
true omar { name: 'عمر', age: 15, city: 'الإسكندرية', score: 78 }
  • getDoc تُرجع لقطة (Snapshot) للمستند.
  • exists(): هل المستند موجود؟ لمستند غير موجود ستُرجع false.
  • data(): الحقول ككائن JavaScript عادي.

بعد إضافة سارة وعمر ومنى ويوسف (من القاهرة بدرجة 64)، نريد طلاب القاهرة مرتبين بالدرجة:

import { query, where, orderBy, getDocs } from 'firebase/firestore';
const q = query(
collection(db, 'students'),
where('city', '==', 'القاهرة'),
orderBy('score', 'desc')
);
const result = await getDocs(q);
result.forEach((d) => console.log(d.data().name, d.data().score));
الناتج
سارة 92
يوسف 64
  • where(الحقل, المقارنة, القيمة): المقارنات == و != و < و <= و > و >=، و in و array-contains للمصفوفات.
  • orderBy('score', 'desc'): الترتيب، و 'asc' للتصاعدي.
  • limit(n): أقصى عدد للنتائج.
  • getDocs: تُرجع مجموعة لقطات، نمر عليها بـ forEach.
import { updateDoc, increment, arrayUnion } from 'firebase/firestore';
await updateDoc(doc(db, 'students', 'omar'), {
score: increment(5),
hobbies: arrayUnion('كرة القدم')
});

بعدها يصبح مستند عمر:

الناتج
{ name: 'عمر', age: 15, city: 'الإسكندرية', score: 83, hobbies: [ 'كرة القدم' ] }
  • updateDoc: يعدّل الحقول المذكورة فقط.
  • increment(5): يزيد الرقم 5، وهو آمن عندما يعدّل عدة مستخدمين نفس الرقم في نفس اللحظة.
  • arrayUnion: يضيف عنصراً لمصفوفة دون تكرار، وينشئها إن لم تكن موجودة.
import { deleteDoc } from 'firebase/firestore';
await deleteDoc(doc(db, 'students', 'youssef'));

السحر: التحديث الفوري بـ onSnapshot

Section titled “السحر: التحديث الفوري بـ onSnapshot”

بدلاً من قراءة البيانات مرة واحدة، يمكنك الاستماع لها:

import { onSnapshot } from 'firebase/firestore';
const unsubscribe = onSnapshot(doc(db, 'students', 'mona'), (snap) => {
console.log('الدرجة الآن:', snap.data().score);
});

الدالة تُنفَّذ فوراً بالقيمة الحالية (85)، ثم تُنفَّذ تلقائياً كلما تغير المستند. عندما عدّلنا درجة منى إلى 90 من مكان آخر، وصلت القيمتان بالترتيب: 85 ثم 90، دون أي طلب جديد منا.

هكذا تُبنى تطبيقات المحادثة ولوحات النتائج المباشرة: كل مستخدم «يستمع» للبيانات، والتغييرات تصل للجميع فوراً. وعندما لا تحتاج الاستماع، مثلاً عند مغادرة الصفحة، استدعِ unsubscribe() لإيقافه.

قواعد الأمان: لمحة أولى

Section titled “قواعد الأمان: لمحة أولى”

افتراضياً، يجب أن تحدد من يقرأ ويكتب. قواعد الأمان تُكتب في Console أو في ملف firestore.rules:

firestore.rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /students/{id} {
allow read: if true;
allow write: if request.auth != null;
}
}
}

القراءة متاحة للجميع، والكتابة للمستخدمين المسجلين فقط. ستفهم request.auth في الدرس القادم عن المصادقة.

  1. أنشئ Firestore في مشروعك، أو شغّل المحاكي.
  2. أضف 5 كتب لمجموعة books، اثنان منها بـ setDoc بمعرّفات من اختيارك.
  3. اعرض الكتب التي سعرها أقل من 100 مرتبة بالسعر.
  4. زِد عدد نسخ كتاب بـ increment.
  5. اكتب onSnapshot على كتاب، ثم عدّله من Firebase Console، وشاهد التحديث يصل فوراً.
  • نسيان await: تحصل على Promise بدلاً من البيانات، أو تُنفَّذ الأسطر بترتيب غير متوقع.
  • setDoc بدل updateDoc: يمسح الحقول غير المذكورة.
  • ترك القواعد مفتوحة للجميع: القاعدة allow read, write: if true على بيانات حقيقية تعني أن أي شخص يستطيع حذف كل بياناتك.

سؤال

ما الفرق بين addDoc و setDoc؟

سؤال

ما الذي يميز onSnapshot عن getDoc؟

  • Firestore تنظم البيانات في مجموعات ومستندات، وكل العمليات غير متزامنة مع await.
  • addDoc بمعرّف تلقائي، و setDoc بمعرّف تختاره، و updateDoc لتعديل حقول محددة.
  • getDoc لمستند واحد، و query مع where و orderBy و limit ثم getDocs للاستعلام.
  • increment و arrayUnion تعديلات آمنة، و deleteDoc للحذف.
  • onSnapshot للتحديث الفوري، وقواعد الأمان تحمي البيانات.