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…