Kodax

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'));
اختبر فهمك