iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Firestore Queries

Firestore queries filter, order, and paginate collection reads. They’re indexed automatically for single-field; composite queries need a composite index that Firebase prompts you to create.

where, orderBy, pagination, real-time

EXAMPLE
import {
    collection, query, where, orderBy, limit, startAfter,
    getDocs, onSnapshot, doc, getDoc,
} from 'firebase/firestore';
import { db } from './firebase';

// 1) Basic filter
const q = query(
    collection(db, 'posts'),
    where('status', '==', 'published'),
);
const snap = await getDocs(q);
const posts = snap.docs.map(d => ({ id: d.id, ...d.data() }));

// 2) Multiple where + orderBy + limit (needs composite index)
const recentNews = query(
    collection(db, 'posts'),
    where('status', '==', 'published'),
    where('category', '==', 'news'),
    orderBy('createdAt', 'desc'),
    limit(20),
);

// 3) Range filters — only ONE field can have inequality
const expensive = query(
    collection(db, 'products'),
    where('price', '>=', 100),
    where('price', '<=', 500),
    orderBy('price'),
);

// 4) array-contains — single value
const tagged = query(
    collection(db, 'posts'),
    where('tags', 'array-contains', 'featured'),
);

// 5) array-contains-any / in — multiple values (up to 30)
const orQuery = query(
    collection(db, 'posts'),
    where('category', 'in', ['news', 'sport', 'finance']),
);

// 6) Cursor pagination — use last doc as the cursor
const page1 = await getDocs(query(
    collection(db, 'posts'),
    orderBy('createdAt', 'desc'),
    limit(20),
));

const lastVisible = page1.docs[page1.docs.length - 1];

const page2 = await getDocs(query(
    collection(db, 'posts'),
    orderBy('createdAt', 'desc'),
    startAfter(lastVisible),
    limit(20),
));

// 7) Real-time updates — onSnapshot
const unsub = onSnapshot(
    query(collection(db, 'chats', chatId, 'messages'), orderBy('ts'), limit(50)),
    (snap) => {
        snap.docChanges().forEach((change) => {
            if (change.type === 'added')    appendMessage(change.doc.data());
            if (change.type === 'modified') updateMessage(change.doc.data());
            if (change.type === 'removed')  removeMessage(change.doc.id);
        });
    },
);
// Call unsub() when the component unmounts.

// 8) Read a single doc
const snap = await getDoc(doc(db, 'users', uid));
if (snap.exists()) console.log(snap.data());

// 9) Subcollections
const messagesRef = collection(db, 'chats', chatId, 'messages');

// 10) Aggregate (count / sum / avg) — server-side, no full read
import { getCountFromServer, sum, average, aggregateField } from 'firebase/firestore';
const countSnap = await getCountFromServer(query(collection(db, 'orders'), where('status', '==', 'paid')));
console.log(countSnap.data().count);

// 11) Best practices
//   • Denormalise — Firestore has no JOINs; duplicate data into the read shape
//   • Limit reads with where + limit — every read costs money
//   • Use real-time onSnapshot for live UIs; getDocs for one-off reads
//   • Set security rules — never trust the client

Why it matters

Firestore charges per document read. Always pair orderBy with limit, paginate with cursors instead of offsets, and watch onSnapshot subscription churn — each re-subscription re-reads.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
import { collection, query, where, orderBy, limit, getDocs } from 'firebase/firestore';
const q = query(
    collection(db, 'posts'),
    where('published', '==', true),
    orderBy('created', 'desc'),
    limit(10)
);
const snap = await getDocs(q);
Try it Yourself »

Discussion

Loading…