Firestore CRUD — Firebase
عمليات Firestore (CRUD)
الإنشاء
import { addDoc, collection, doc, serverTimestamp, setDoc } from "firebase/firestore";
import { db } from "./firebase.js";
export async function createPost(uid, title) {
const ref = await addDoc(collection(db, "posts"), { // معرّف تلقائي
title,
authorId: uid,
createdAt: serverTimestamp(), // وقت الخادم لا جهاز المستخدم
});
return ref.id;
}
export async function createProfile(uid, name) {
await setDoc(doc(db, "users", uid), { name }, { merge: true }); // معرّف محدد (uid)
}| الدالة | الفرق |
|---|---|
| addDoc | معرّف عشوائي تولده Firestore |
| setDoc | معرّف تحدده أنت؛ يستبدل الوثيقة كلها إلا مع { merge: true } |
| updateDoc | يعدّل حقولاً محددة؛ يفشل إن لم توجد الوثيقة |
القراءة
import { collection, doc, getDoc, getDocs, limit, orderBy, query, where } from "firebase/firestore";
import { db } from "./firebase.js";
export async function getPost(id) {
const snap = await getDoc(doc(db, "posts", id));
return snap.exists() ? { id: snap.id, ...snap.data() } : null;
}
export async function latestPostsBy(uid) {
const q = query(collection(db, "posts"), where("authorId", "==", uid), orderBy("createdAt", "desc"), limit(20));
const snap = await getDocs(q);
return snap.docs.map((d) => ({ id: d.id, ...d.data() }));
}- استعلام يجمع where على حقل و orderBy على آخر يحتاج فهرساً مركباً؛ Firestore يعطيك رابط إنشائه في رسالة الخطأ.
- لا يوجد LIKE للبحث النصي؛ للبحث استخدم خدمة مخصصة (Algolia، Typesense) أو حقول بحث مجهزة مسبقاً.
الترقيم
startAfter(lastDoc) مع limit: احتفظ بآخر وثيقة في الصفحة واطلب ما بعدها. لا يوجد offset رخيص (القفز يُحاسب كقراءات).
التحديث والحذف
- updateDoc(ref, { likes: increment(1) }) — زيادة ذرية.
- arrayUnion و arrayRemove للمصفوفات.
- deleteDoc(ref) — ⚠️ لا يحذف المجموعات الفرعية تحتها؛ تبقى يتيمة.
الاستماع اللحظي
import { collection, onSnapshot, orderBy, query } from "firebase/firestore";
import { db } from "./firebase.js";
export function watchPosts(onChange) {
const q = query(collection(db, "posts"), orderBy("createdAt", "desc"));
return onSnapshot(q, (snap) => onChange(snap.docs.map((d) => ({ id: d.id, ...d.data() })))); // يعيد دالة الإلغاء
}ألغِ المستمع عند مغادرة الشاشة. وكل وثيقة تتغير تُحسب قراءة لكل مستمع — راقب التكلفة.
العمليات الجماعية
writeBatch (حتى 500 كتابة كوحدة واحدة) و runTransaction (قراءة ثم كتابة بأمان مع التزامن).
تمرين
اكتب دوال منشورات مع: إنشاء، جلب صفحة من 10 بالترقيم بـ startAfter، إعجاب بـ increment، واستماع لحظي لعدد الإعجابات على منشور واحد.
import {
collection, doc,
getDoc, getDocs,
addDoc, setDoc, updateDoc, deleteDoc,
query, where, orderBy, limit
} from 'firebase/firestore';
// ===== إضافة document =====
// مع ID تلقائي
const docRef = await addDoc(collection(db, 'users'), {
name: 'أحمد',
age: 25
});
// مع ID محدد
await setDoc(doc(db, 'users', 'user123'), {
name: 'سارة',
age: 22
});
// ===== قراءة document واحد =====
const docSnap = await getDoc(doc(db, 'users', 'user123'));
if (docSnap.exists()) {
console.log(docSnap.data());
}
// ===== قراءة collection =====
const querySnapshot = await getDocs(collection(db, 'users'));
const users = querySnapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
// ===== استعلام مع فلترة =====
const q = query(
collection(db, 'users'),
where('age', '>=', 18),
orderBy('age', 'desc'),
limit(10)
);
const results = await getDocs(q);
// ===== تحديث =====
await updateDoc(doc(db, 'users', 'user123'), {
age: 23
});
// ===== حذف =====
await deleteDoc(doc(db, 'users', 'user123'));اختبر فهمك