دليل شامل: استخدام Firestore مع JavaScript لبناء تطبيقات ويب قوية
مقدمة إلى Firestore و JavaScript: دليل عملي متكامل
تُعد Google Cloud Firestore قاعدة بيانات NoSQL سحابية، لا تتطلب خادمًا، وتتميز بالسرعة الفائقة والمرونة، مما يجعلها الخيار الأمثل لتشغيل تطبيقات الويب والجوال بمختلف أحجامها. يهدف هذا الدليل الشامل إلى تزويدك بالمعرفة والمهارات اللازمة لاستخدام Firestore كمحرك أساسي لمشاريعك المذهلة، من الواجهة الأمامية إلى الخلفية، من خلال أمثلة عملية وواضحة.
جدول المحتويات
- البدء مع Firestore
- ما هو Firestore؟ ولماذا يجب عليك استخدامه؟
- إعداد Firestore في مشروع JavaScript
- مستندات ومجموعات Firestore
- إدارة قاعدة بياناتنا باستخدام لوحة تحكم Firebase
- جلب البيانات باستخدام Firestore
- تغيير البيانات باستخدام Firestore
- أنماط أساسية للعمل مع Firestore
- الخلاصة التقنية
ما هو Firestore؟ ولماذا يجب عليك استخدامه؟
تُعد Firestore قاعدة بيانات مرنة وسهلة الاستخدام للغاية لتطوير تطبيقات الجوال والويب والخوادم. إذا كنت على دراية بقاعدة بيانات Realtime Database من Firebase، فإن Firestore تشترك معها في العديد من أوجه التشابه، ولكن مع واجهة برمجة تطبيقات (API) مختلفة (يمكن القول إنها أكثر تصريحية). إليك بعض الميزات البارزة التي تقدمها Firestore:
⚡️ سهولة الحصول على البيانات في الوقت الفعلي
تمامًا مثل قاعدة بيانات Firebase Realtime Database، توفر Firestore طرقًا مفيدة مثل .onSnapshot() التي تجعل من السهل جدًا الاستماع إلى التحديثات على بياناتك في الوقت الفعلي. هذا يجعل Firestore خيارًا مثاليًا للمشاريع التي تولي أهمية قصوى لعرض واستخدام أحدث البيانات (مثل تطبيقات الدردشة).
مرونة كقاعدة بيانات NoSQL
تُعد Firestore خيارًا مرنًا للغاية للواجهة الخلفية لأنها قاعدة بيانات NoSQL. يعني NoSQL أن البيانات لا تُخزن في جداول وأعمدة كما تفعل قاعدة بيانات SQL القياسية. بدلاً من ذلك، يتم تنظيمها كمتجر مفتاح-قيمة (key-value store)، كما لو كانت كائن JavaScript واحدًا كبيرًا. بعبارة أخرى، لا يوجد مخطط (schema) أو حاجة لوصف البيانات التي ستخزنها قاعدة بياناتنا. طالما أننا نقدم مفاتيح وقيمًا صالحة، ستقوم Firestore بتخزينها.
↕️ قابلية التوسع بسهولة
إحدى الفوائد العظيمة لاختيار Firestore لقاعدة بياناتك هي البنية التحتية القوية جدًا التي تعتمد عليها، والتي تمكنك من توسيع تطبيقك بسهولة بالغة، سواء رأسيًا أو أفقيًا. بغض النظر عما إذا كان لديك المئات أو الملايين من المستخدمين، ستكون خوادم Google قادرة على التعامل مع أي حمل تضعه عليها. باختصار، Firestore خيار رائع للتطبيقات الصغيرة والكبيرة على حد سواء. بالنسبة للتطبيقات الصغيرة، فهي قوية لأننا نستطيع إنجاز الكثير دون إعداد كبير وإنشاء مشاريع بسرعة كبيرة. أما للمشاريع الكبيرة، فإن Firestore مناسبة تمامًا بسبب قابليتها للتوسع.
إعداد Firestore في مشروع JavaScript
سنستخدم حزمة تطوير البرامج (SDK) الخاصة بـ Firestore للغة JavaScript. سنغطي في هذا الدليل كيفية استخدام Firestore ضمن سياق مشروع JavaScript. ومع ذلك، فإن المفاهيم التي سنغطيها هنا قابلة للتحويل بسهولة إلى أي من مكتبات عميل Firestore المتاحة.
للبدء مع Firestore، سنتجه إلى لوحة تحكم Firebase. يمكنك زيارتها بالانتقال إلى firebase.google.com. ستحتاج إلى حساب Google لتسجيل الدخول.

بمجرد تسجيل الدخول، سنقوم بإنشاء مشروع جديد وإعطائه اسمًا.

بعد إنشاء مشروعنا، سنقوم بتحديده. بعد ذلك، في لوحة تحكم مشروعنا، سنختار زر الكود. سيعطينا هذا الكود الذي نحتاجه لدمج Firestore مع مشروع JavaScript الخاص بنا.

عادةً، إذا كنت تقوم بإعداد هذا في أي نوع من تطبيقات JavaScript، فستحتاج إلى وضع هذا في ملف مخصص يسمى firebase.js. إذا كنت تستخدم أي مكتبة JavaScript تحتوي على ملف package.json، فستحتاج إلى تثبيت تبعية Firebase باستخدام npm أو yarn.
// with npm
npm i firebase
// with yarn
yarn add firebase
يمكن استخدام Firestore إما على جانب العميل (client-side) أو الخادم (server-side). إذا كنت تستخدم Firestore مع Node.js، فستحتاج إلى استخدام صيغة CommonJS مع require. بخلاف ذلك، إذا كنت تستخدم JavaScript في العميل، فستقوم باستيراد Firebase باستخدام وحدات ES Modules.
// with CommonJS syntax (if using Node)
const firebase = require("firebase/app");
require("firebase/firestore");
// with ES Modules (if using client-side JS, like React)
import firebase from 'firebase/app';
import 'firebase/firestore';
var firebaseConfig = {
apiKey: "AIzaSyDpLmM79mUqbMDBexFtOQOkSl0glxCW_ds",
authDomain: "lfasdfkjkjlkjl.firebaseapp.com",
databaseURL: "https://lfasdlkjkjlkjl.firebaseio.com",
projectId: "lfasdlkjkjlkjl",
storageBucket: "lfasdlkjkjlkjl.appspot.com",
messagingSenderId: "616270824980",
appId: "1:616270824990:web:40c8b177c6b9729cb5110f",
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
مستندات ومجموعات Firestore
هناك مصطلحان أساسيان ضروريان لفهم كيفية العمل مع Firestore: documents (المستندات) و collections (المجموعات). المستندات هي قطع فردية من البيانات في قاعدة بياناتنا. يمكنك التفكير في المستندات على أنها تشبه كائنات JavaScript البسيطة. تتكون من أزواج مفتاح-قيمة (key-value pairs)، والتي نشير إليها باسم fields (الحقول). يمكن أن تكون قيم هذه الحقول سلاسل نصية (strings)، أو أرقامًا (numbers)، أو قيمًا منطقية (Booleans)، أو كائنات (objects)، أو مصفوفات (arrays)، وحتى بيانات ثنائية (binary data).
document -> { key: value }
مجموعات هذه المستندات تُعرف باسم collections. تشبه المجموعات إلى حد كبير مصفوفات من الكائنات (arrays of objects). داخل المجموعة، يرتبط كل مستند بمعرف (id) معين.
collection -> [{ id: doc }, { id: doc }]
إدارة قاعدة بياناتنا باستخدام لوحة تحكم Firebase
قبل أن نتمكن من البدء فعليًا في العمل مع قاعدة بياناتنا، نحتاج إلى إنشائها. داخل لوحة تحكم Firebase الخاصة بنا، انتقل إلى علامة التبويب Database وقم بإنشاء قاعدة بيانات Firestore الخاصة بك.

بمجرد الانتهاء من ذلك، سنبدأ في وضع الاختبار (test mode) ونمكّن جميع عمليات القراءة والكتابة على قاعدة بياناتنا. بعبارة أخرى، سيكون لدينا وصول مفتوح للحصول على البيانات وتغييرها في قاعدة بياناتنا. إذا أضفنا مصادقة Firebase Authentication، فيمكننا تقييد الوصول للمستخدمين المصادق عليهم فقط.
بعد ذلك، سيتم نقلنا إلى قاعدة البيانات نفسها، حيث يمكننا البدء في إنشاء المجموعات والمستندات. سيكون جذر قاعدة بياناتنا عبارة عن سلسلة من المجموعات، لذا دعنا ننشئ مجموعتنا الأولى. يمكننا تحديد Start collection وإعطائها معرفًا (id). ستحتوي كل مجموعة على معرف أو اسم. لمشروعنا، سنحتفظ بسجل للكتب المفضلة للمستخدمين. سنعطي مجموعتنا الأولى المعرف books.

بعد ذلك، سنضيف مستندنا الأول إلى مجموعة books التي أنشأناها حديثًا. سيحتوي كل مستند على معرف (id) أيضًا، يربطه بالمجموعة التي يوجد فيها. في معظم الحالات، سنستخدم خيارًا لمنحه معرفًا يتم إنشاؤه تلقائيًا. لذا يمكننا النقر على زر auto id للقيام بذلك، وبعد ذلك نحتاج إلى توفير حقل (field)، وإعطائه نوعًا (type)، بالإضافة إلى قيمة (value). لكتابنا الأول، سنجعل حقل title من النوع string، مع القيمة The Great Gatsby، ثم نضغط على حفظ. بعد ذلك، يجب أن نرى أول عنصر لدينا في قاعدة بياناتنا.

جلب البيانات باستخدام Firestore
للوصول إلى Firestore واستخدام جميع الطرق التي يوفرها، نستخدم firebase.firestore(). يجب تنفيذ هذه الطريقة في كل مرة نريد التفاعل فيها مع قاعدة بيانات Firestore الخاصة بنا. أوصي بإنشاء متغير مخصص لتخزين مرجع واحد إلى Firestore. يساعد القيام بذلك على تقليل كمية الكود الذي تكتبه عبر تطبيقك.
const db = firebase.firestore();
في هذا الدليل، ومع ذلك، سألتزم باستخدام طريقة firestore في كل مرة لأكون واضحًا قدر الإمكان. للإشارة إلى مجموعة، نستخدم طريقة .collection() ونقدم معرف المجموعة كوسيطة. للحصول على مرجع لمجموعة الكتب التي أنشأناها، ما عليك سوى تمرير السلسلة النصية 'books'.
const booksRef = firebase.firestore().collection('books');
الحصول على البيانات من مجموعة باستخدام .get()
للحصول على جميع بيانات المستندات من مجموعة، يمكننا ربط طريقة .get(). تُرجع .get() وعدًا (promise)، مما يعني أنه يمكننا حله إما باستخدام دالة رد الاتصال .then() أو يمكننا استخدام صيغة async-await إذا كنا ننفذ الكود الخاص بنا داخل دالة غير متزامنة (async function). بمجرد حل وعدنا بطريقة أو بأخرى، نحصل على ما يُعرف بـ snapshot (لقطة). بالنسبة لاستعلام مجموعة، ستتكون هذه اللقطة من عدد من المستندات الفردية. يمكننا الوصول إليها بقول snapshot.docs. من كل مستند، يمكننا الحصول على المعرف (id) كخاصية منفصلة، وبقية البيانات باستخدام طريقة .data(). إليك كيف يبدو استعلامنا بالكامل:
const booksRef = firebase
.firestore()
.collection("books");
booksRef
.get()
.then((snapshot) => {
const data = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
console.log("All data in 'books' collection", data);
// [ { id: 'glMeZvPpTN1Ah31sKcnj', title: 'The Great Gatsby' } ]
});
الاشتراك في مجموعة باستخدام .onSnapshot()
تعيد طريقة .get() ببساطة جميع البيانات داخل مجموعتنا. للاستفادة من بعض إمكانيات Firestore في الوقت الفعلي، يمكننا الاشتراك في مجموعة، مما يمنحنا القيمة الحالية للمستندات في تلك المجموعة، كلما تم تحديثها. بدلاً من استخدام طريقة .get()، التي تستخدم للاستعلام لمرة واحدة، نستخدم طريقة .onSnapshot().
firebase
.firestore()
.collection("books")
.onSnapshot((snapshot) => {
const data = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
console.log("All data in 'books' collection", data);
});
في الكود أعلاه، نستخدم ما يُعرف بـ method chaining (سلسلة الطرق) بدلاً من إنشاء متغير منفصل للإشارة إلى المجموعة. ما يميز استخدام Firestore هو أنه يمكننا ربط مجموعة من الطرق الواحدة تلو الأخرى، مما يجعل الكود أكثر تصريحية وقابلية للقراءة. داخل دالة رد الاتصال الخاصة بـ onSnapshot، نحصل على وصول مباشر إلى لقطة مجموعتنا، سواء الآن أو كلما تم تحديثها في المستقبل. حاول تحديث مستندنا يدويًا وسترى أن .onSnapshot() تستمع لأي تغييرات في هذه المجموعة.
الفرق بين .get() و .onSnapshot()
يكمن الفرق بين طريقتي .get() و .onSnapshot() في أن .get() تُرجع وعدًا (promise) يحتاج إلى حل، وعندها فقط نحصل على بيانات اللقطة. أما .onSnapshot()، فتستخدم دالة رد اتصال متزامنة (synchronous callback function)، مما يمنحنا وصولًا مباشرًا إلى اللقطة. من المهم أن نضع هذا في الاعتبار عند التعامل مع هذه الطرق المختلفة – يجب أن نعرف أي منها يُرجع وعدًا وأيها متزامن.
إلغاء الاشتراك من مجموعة باستخدام unsubscribe()
لاحظ أيضًا أن .onSnapshot() تُرجع دالة يمكننا استخدامها لإلغاء الاشتراك والتوقف عن الاستماع إلى مجموعة معينة. هذا مهم في الحالات التي يغادر فيها المستخدم، على سبيل المثال، صفحة معينة حيث نعرض بيانات مجموعة. إليك مثال، باستخدام مكتبة React حيث نستدعي unsubscribe داخل خطاف useEffect. عندما نفعل ذلك، سيضمن هذا أنه عندما يتم إلغاء تحميل المكون (unmounted) (لم يعد معروضًا ضمن سياق تطبيقنا) أننا لم نعد نستمع إلى بيانات المجموعة التي نستخدمها في هذا المكون.
function App() {
const [books, setBooks] = React.useState([]);
React.useEffect(() => {
const unsubscribe = firebase
.firestore()
.collection("books")
.onSnapshot((snapshot) => {
const data = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
setBooks(data);
});
return unsubscribe;
}, []);
return books.map(book => <BookList key={book.id} book={book} />)
}
الحصول على مستندات فردية باستخدام .doc()
عندما يتعلق الأمر بالحصول على مستند داخل مجموعة، فإن العملية هي نفسها تمامًا مثل الحصول على مجموعة كاملة: نحتاج أولاً إلى إنشاء مرجع لذلك المستند، ثم استخدام طريقة .get() لجلبه. بعد ذلك، ومع ذلك، نستخدم طريقة .doc() المرتبطة بطريقة المجموعة. لإنشاء مرجع، نحتاج إلى الحصول على هذا المعرف (id) من قاعدة البيانات إذا تم إنشاؤه تلقائيًا. بعد ذلك، يمكننا ربط .get() وحل الوعد (promise).
const bookRef = firebase
.firestore()
.collection("books")
.doc("glMeZvPpTN1Ah31sKcnj");
bookRef.get().then((doc) => {
if (!doc.exists) return;
console.log("Document data:", doc.data());
// Document data: { title: 'The Great Gatsby' }
});
لاحظ الشرط if (!doc.exists) return; في الكود أعلاه. بمجرد استعادة المستند، من الضروري التحقق مما إذا كان موجودًا. إذا لم نفعل ذلك، سيحدث خطأ في الحصول على بيانات المستند. طريقة التحقق مما إذا كان مستندنا موجودًا هي بقول if (doc.exists)، والذي يُرجع قيمة true أو false. إذا أعاد هذا التعبير false، فنحن نريد العودة من الدالة أو ربما إلقاء خطأ. إذا كانت doc.exists صحيحة، يمكننا الحصول على البيانات من doc.data().
تغيير البيانات باستخدام Firestore
إضافة مستند إلى مجموعة باستخدام .add()
بعد ذلك، دعنا ننتقل إلى تغيير البيانات. أسهل طريقة لإضافة مستند جديد إلى مجموعة هي باستخدام طريقة .add(). كل ما عليك فعله هو تحديد مرجع مجموعة (باستخدام .collection()) وربط .add(). بالعودة إلى تعريفنا للمستندات على أنها تشبه كائنات JavaScript، نحتاج إلى تمرير كائن إلى طريقة .add() وتحديد جميع الحقول التي نريد أن تكون في المستند. لنفترض أننا نريد إضافة كتاب آخر، 'Of Mice and Men':
firebase
.firestore()
.collection("books")
.add({
title: "Of Mice and Men",
})
.then((ref) => {
console.log("Added doc with ID: ", ref.id);
// Added doc with ID: ZzhIgLqELaoE3eSsOazu
});
تُرجع طريقة .add() وعدًا (promise)، ومن هذا الوعد المحلول، نحصل على مرجع إلى المستند الذي تم إنشاؤه، والذي يمنحنا معلومات مثل المعرف (id) الذي تم إنشاؤه. تقوم طريقة .add() بإنشاء معرف تلقائيًا لنا. لاحظ أنه لا يمكننا استخدام هذا المرجع مباشرة للحصول على البيانات. ومع ذلك، يمكننا تمرير المرجع إلى طريقة .doc() لإنشاء استعلام آخر.
إضافة مستند إلى مجموعة باستخدام .set()
هناك طريقة أخرى لإضافة مستند إلى مجموعة وهي باستخدام طريقة .set(). يكمن الاختلاف بين set و add في الحاجة إلى تحديد معرفنا الخاص عند إضافة البيانات. يتطلب هذا ربط طريقة .doc() بالمعرف الذي تريد استخدامه. لاحظ أيضًا أنه عندما يتم حل الوعد من .set()، لا نحصل على مرجع إلى المستند الذي تم إنشاؤه:
firebase
.firestore()
.collection("books")
.doc("another book")
.set({
title: "War and Peace",
})
.then(() => {
console.log("Document created");
});
بالإضافة إلى ذلك، عندما نستخدم .set() مع مستند موجود، فإنه سيقوم، افتراضيًا، بالكتابة فوق هذا المستند. إذا أردنا دمج مستند قديم مع مستند جديد بدلاً من الكتابة فوقه، نحتاج إلى تمرير وسيطة إضافية إلى .set() وتوفير الخاصية merge مضبوطة على true.
// use .set() to merge data with existing document, not overwrite
const bookRef = firebase
.firestore()
.collection("books")
.doc("another book");
bookRef
.set({ author: "Lev Nikolaevich Tolstoy" }, { merge: true })
.then(() => {
console.log("Document merged");
bookRef
.get()
.then(doc => {
console.log("Merged document: ", doc.data());
// Merged document: { title: 'War and Peace', author: 'Lev Nikolaevich Tolstoy' }
});
});
تحديث البيانات الموجودة باستخدام .update()
عندما يتعلق الأمر بتحديث البيانات، نستخدم طريقة .update(). مثل .add() و .set()، فإنها تُرجع وعدًا (promise). ما يميز استخدام .update() هو أنها، على عكس .set()، لن تقوم بالكتابة فوق المستند بأكمله. كما هو الحال مع .set()، نحتاج إلى الإشارة إلى مستند فردي. عند استخدام .update()، من المهم استخدام بعض معالجة الأخطاء، مثل دالة رد الاتصال .catch() في حالة عدم وجود المستند.
const bookRef = firebase.firestore().collection("books").doc("another book");
bookRef
.update({
year: 1869,
})
.then(() => {
console.log("Document updated");
// Document updated
})
.catch((error) => {
console.error("Error updating doc", error);
});
حذف البيانات باستخدام .delete()
يمكننا حذف مجموعة مستندات معينة بالإشارة إليها بمعرفها وتنفيذ طريقة .delete()، الأمر بهذه البساطة. كما أنها تُرجع وعدًا (promise). إليك مثال أساسي لحذف كتاب بمعرف "another book":
firebase
.firestore()
.collection("books")
.doc("another book")
.delete()
.then(() => console.log("Document deleted"))
// Document deleted
.catch((error) => console.error("Error deleting document", error));
لاحظ أن وثائق Firestore الرسمية لا توصي بحذف مجموعات كاملة، بل مستندات فردية فقط.
أنماط أساسية للعمل مع Firestore
العمل مع المجموعات الفرعية (Subcollections)
لنفترض أننا ارتكبنا خطأ في إنشاء تطبيقنا، وبدلاً من مجرد إضافة كتب، نريد أيضًا ربطها بالمستخدمين الذين أنشأوها. الطريقة التي نريد بها إعادة هيكلة البيانات هي عن طريق إنشاء مجموعة تسمى 'users' في جذر قاعدة بياناتنا، وجعل 'books' مجموعة فرعية (subcollection) من 'users'. سيسمح هذا للمستخدمين بامتلاك مجموعاتهم الخاصة من الكتب. كيف نقوم بإعداد ذلك؟ يجب أن تبدو المراجع إلى المجموعة الفرعية 'books' كما يلي:
const userBooksRef = firebase
.firestore()
.collection('users')
.doc('user-id')
.collection('books');
لاحظ أيضًا أنه يمكننا كتابة كل هذا ضمن استدعاء .collection() واحد باستخدام الشرطات المائلة الأمامية (forward slashes). الكود أعلاه مكافئ لما يلي، حيث يجب أن يحتوي مرجع المجموعة على عدد فردي من الأجزاء. إذا لم يكن كذلك، ستُلقي Firestore خطأ.
const userBooksRef = firebase
.firestore()
.collection('users/user-id/books');
لإنشاء المجموعة الفرعية نفسها، مع مستند واحد (رواية أخرى لـ Steinbeck، 'East of Eden')، قم بتشغيل ما يلي.
firebase.firestore().collection("users/user-1/books").add({
title: "East of Eden",
});
بعد ذلك، سيبدو الحصول على هذه المجموعة الفرعية التي تم إنشاؤها حديثًا كما يلي بناءً على معرف المستخدم (user’s ID).
firebase
.firestore()
.collection("users/user-1/books")
.get()
.then((snapshot) => {
const data = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
console.log(data);
// [ { id: 'UO07aqpw13xvlMAfAvTF', title: 'East of Eden' } ]
});
طرق مفيدة لحقول Firestore
هناك بعض الأدوات المفيدة التي يمكننا الحصول عليها من Firestore والتي تمكننا من العمل مع قيم حقولنا بسهولة أكبر. على سبيل المثال، يمكننا إنشاء طابع زمني (timestamp) عندما يتم إنشاء أو تحديث مستند معين باستخدام المساعد التالي من خاصية FieldValue. يمكننا بالطبع إنشاء قيم تاريخ خاصة بنا باستخدام JavaScript، ولكن استخدام طابع زمني للخادم (server timestamp) يتيح لنا معرفة متى تم تغيير البيانات أو إنشاؤها بالضبط من Firestore نفسها.
firebase
.firestore()
.collection("users")
.doc("user-2")
.set({
created: firebase.firestore.FieldValue.serverTimestamp(),
})
.then(() => {
console.log("Added user");
// Added user
});
بالإضافة إلى ذلك، لنفترض أن لدينا حقلًا في مستند يتتبع عددًا معينًا، مثل عدد الكتب التي أنشأها المستخدم. كلما أنشأ المستخدم كتابًا جديدًا، نريد زيادة هذا العدد بواحد. طريقة سهلة للقيام بذلك، بدلاً من الاضطرار إلى إجراء طلب .get() أولاً، هي استخدام مساعد قيمة حقل آخر يسمى .increment():
const userRef = firebase.firestore().collection("users").doc("user-2");
userRef
.set({
count: firebase.firestore.FieldValue.increment(1),
})
.then(() => {
console.log("Updated user");
userRef.get().then((doc) => {
console.log("Updated user data: ", doc.data());
});
});
الاستعلام باستخدام .where()
ماذا لو أردنا الحصول على بيانات من مجموعاتنا بناءً على شروط معينة؟ على سبيل المثال، لنفترض أننا نريد الحصول على جميع المستخدمين الذين قدموا كتابًا واحدًا أو أكثر؟ يمكننا كتابة مثل هذا الاستعلام بمساعدة طريقة .where(). أولاً، نشير إلى مجموعة ثم نربط .where(). تأخذ طريقة where ثلاث وسائط – أولاً، الحقل الذي نبحث فيه، ثم عامل تشغيل (operator)، ثم القيمة التي نريد تصفية مجموعتنا بناءً عليها. يمكننا استخدام أي من عوامل التشغيل التالية، ويمكن أن تكون الحقول التي نستخدمها قيمًا بدائية (primitive values) بالإضافة إلى مصفوفات (arrays): <، <=، ==، >، >=، array-contains، in، أو array-contains-any.
لجلب جميع المستخدمين الذين قدموا أكثر من كتاب واحد، يمكننا استخدام الاستعلام التالي. بعد .where()، نحتاج إلى ربط .get(). عند حل وعدنا، نحصل على ما يُعرف بـ querySnapshot. تمامًا مثل الحصول على مجموعة، يمكننا التكرار فوق querySnapshot باستخدام .map() للحصول على معرف كل مستند وبياناته (الحقول):
firebase
.firestore()
.collection("users")
.where("count", ">=", 1)
.get()
.then((querySnapshot) => {
const data = querySnapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
console.log("Users with > 1 book: ", data);
// Users with > 1 book: [ { id: 'user-1', count: 1 } ]
});
لاحظ أنه يمكنك ربط طرق .where() متعددة لإنشاء استعلامات مركبة.
ترتيب وتحديد البيانات
طريقة أخرى للاستعلام الفعال عن مجموعاتنا هي تحديدها. لنفترض أننا نريد تحديد استعلام معين لكمية معينة من المستندات. إذا أردنا فقط إرجاع عدد قليل من العناصر من استعلامنا، فنحن بحاجة فقط إلى إضافة طريقة .limit()، بعد مرجع معين. إذا أردنا القيام بذلك من خلال استعلامنا لجلب المستخدمين الذين قدموا كتابًا واحدًا على الأقل، فسيبدو الأمر كما يلي.
const usersRef = firebase
.firestore()
.collection("users")
.where("count", ">=", 1);
usersRef.limit(3);
ميزة قوية أخرى هي ترتيب بياناتنا المستعلم عنها وفقًا لحقول المستندات باستخدام .orderBy(). إذا أردنا ترتيب المستخدمين الذين تم إنشاؤهم حسب وقت إنشائهم لأول مرة، يمكننا استخدام طريقة orderBy مع حقل 'created' كوسيطة أولى. أما الوسيطة الثانية، فنحدد ما إذا كان يجب أن يكون الترتيب تصاعديًا (ascending) أو تنازليًا (descending). للحصول على جميع المستخدمين مرتبين حسب وقت إنشائهم من الأحدث إلى الأقدم، يمكننا تنفيذ الاستعلام التالي:
const usersRef = firebase
.firestore()
.collection("users")
.where("count", ">=", 1);
usersRef.orderBy("created", "desc").limit(3);
يمكننا ربط .orderBy() مع .limit(). لكي يعمل هذا بشكل صحيح، يجب استدعاء .limit() أخيرًا وليس قبل .orderBy().
الخلاصة التقنية
يُظهر هذا الدليل بوضوح أن Firestore، كقاعدة بيانات NoSQL سحابية، توفر مرونة وقابلية توسع استثنائية لتطبيقات JavaScript. من خلال واجهة برمجة تطبيقات (API) بديهية، تسهل Firestore عمليات CRUD (الإنشاء، القراءة، التحديث، الحذف) بشكل كبير، وتبرز بشكل خاص في توفير تحديثات البيانات في الوقت الفعلي عبر طريقة .onSnapshot(). إن فهم الفروق بين documents و collections، وإتقان طرق الاستعلام مثل .where() و .orderBy()، يُمكّن المطورين من بناء تطبيقات ديناميكية وفعالة. كما أن القدرة على استخدام المجموعات الفرعية (subcollections) وإدارة البيانات من خلال لوحة تحكم Firebase Console تعزز من كفاءة سير العمل. بشكل عام، تُعد Firestore خيارًا ممتازًا للمشاريع التي تتطلب معالجة سريعة للبيانات وقابلية توسع عالية دون تعقيدات قواعد البيانات العلائقية التقليدية.