Firestore Queries

Queries let you fetch a specific subset of documents from a collection. Instead of downloading an entire collection and filtering in your app code, Firestore runs the filter on its servers and returns only matching documents. This reduces bandwidth, lowers read counts, and makes your app faster.

How Queries Work

Think of a library with thousands of books. A bad librarian hands you every book and says "find what you want." A good librarian takes your requirements — "science books published after 2020, sorted by title" — walks to the right shelf, and brings back exactly what you asked for. Firestore is the good librarian.

Setting Up a Query

Firestore queries start with a collection reference and chain filter conditions using where, orderBy, and limit:

import { collection, query, where, orderBy, limit, getDocs }
  from "firebase/firestore";
import { db } from "./firebase";

const postsRef = collection(db, "posts");

// Build the query
const q = query(
  postsRef,
  where("authorId", "==", "uid_alice"),
  orderBy("publishedAt", "desc"),
  limit(10)
);

// Execute the query
const snapshot = await getDocs(q);
snapshot.forEach((doc) => {
  console.log(doc.id, doc.data());
});

Where Conditions

The where function takes three arguments: field name, comparison operator, and value.

Available Operators

where("age", "==", 28)          // equals
where("age", "!=", 28)          // not equals
where("age", "<", 30)           // less than
where("age", "<=", 30)          // less than or equal
where("age", ">", 20)           // greater than
where("age", ">=", 20)          // greater than or equal
where("tags", "array-contains", "firebase")   // array has this item
where("tags", "array-contains-any", ["firebase", "react"])  // array has any of these
where("status", "in", ["draft", "published"]) // value is one of these
where("status", "not-in", ["deleted"])        // value is not in list

Combining Multiple Where Conditions

// Posts that are published AND have more than 100 likes
const q = query(
  collection(db, "posts"),
  where("status", "==", "published"),
  where("likesCount", ">=", 100)
);

Multiple where conditions combine with AND logic. Firestore does not support OR queries across different fields directly — but from Firebase v9.7+, the or function handles OR conditions:

import { or } from "firebase/firestore";

// Posts by Alice OR posts with more than 100 likes
const q = query(
  collection(db, "posts"),
  or(
    where("authorId", "==", "uid_alice"),
    where("likesCount", ">", 100)
  )
);

Ordering Results

// Newest posts first
const q = query(
  collection(db, "posts"),
  orderBy("publishedAt", "desc")
);

// Alphabetical order
const q = query(
  collection(db, "posts"),
  orderBy("title", "asc") // asc is the default
);

// Order by multiple fields
const q = query(
  collection(db, "posts"),
  orderBy("category", "asc"),
  orderBy("publishedAt", "desc")
);

Limiting Results

// Get the 5 most recent posts
const q = query(
  collection(db, "posts"),
  orderBy("publishedAt", "desc"),
  limit(5)
);

// Get the 5 oldest posts
const q = query(
  collection(db, "posts"),
  orderBy("publishedAt", "asc"),
  limitToLast(5)
);

Range Filters and Ordering Rules

Firestore has one important rule: if you use a range filter (<, <=, >, >=) on a field, the first orderBy must use the same field.

// CORRECT — range on likesCount, orderBy on likesCount first
const q = query(
  collection(db, "posts"),
  where("likesCount", ">", 50),
  orderBy("likesCount", "desc"),
  orderBy("publishedAt", "desc")
);

// WRONG — range on likesCount, orderBy on publishedAt first
// This throws an error
const q = query(
  collection(db, "posts"),
  where("likesCount", ">", 50),
  orderBy("publishedAt", "desc") // ❌
);

Collection Group Queries

A collection group query searches across all subcollections with the same name, regardless of which parent document they belong to. For example, if every user has a comments subcollection, a collection group query finds all comments across all users:

import { collectionGroup, query, where, getDocs } from "firebase/firestore";

// Find all comments by Alice across all posts
const q = query(
  collectionGroup(db, "comments"),
  where("authorId", "==", "uid_alice")
);

const snapshot = await getDocs(q);
snapshot.forEach((doc) => {
  console.log("Comment:", doc.data().text);
  console.log("Path:", doc.ref.path);
});

Collection group queries require a composite index in Firestore. The console will show an error with a direct link to create the required index if one is missing.

Query Limitations

Firestore queries have specific limitations to be aware of:

  • No full-text search — for search features, use Algolia or Typesense alongside Firestore
  • No OR queries across different fields without the or function (requires index)
  • Range filters work on only one field per query
  • Maximum 30 in values in a single query
  • No inequality filters (!=, not-in) on the same field as an orderBy

Key Takeaway

Firestore queries filter and sort documents on the server before delivering results to your app. Build queries with where, orderBy, and limit chained together using the query function. Remember the range-filter ordering rule, use collection group queries to search across subcollections, and plan for full-text search with an external service like Algolia when your app needs search functionality.

Leave a Comment

Your email address will not be published. Required fields are marked *