Firestore

Firestore is a NoSQL database with support for realtime updates. It is used as the Illust datastore.

One thing to watch out for with Firestore is that there are different libraries for accessing data on the client (firebase/firestore), and on the server (`firebase-admin/firestore).

Client

All queries to Firestore from client apps should go through FirebaseCollections, as this has type information on each collection.

useFirestore

More specifically, React code that queries firestore should generally use the functions defined in useFirestore. These helpers take a function that takes db (the FirestoreCollections), and returns a firestore query to run. When new data comes it, the hook will return the data (with IDs), plus a loading state. You must also pass deps (like with a useEffect to re-run the query and re-start the watch.). There are hooks to query for a single doc by ID or query, and a collection as array or dictionary.

Example:

const useArtPieceBySlug = (slug: string) => {
  return useWatchRecord(
    (db) => query(db.artPieces, where("slug", "==", slug)),
    [slug]
  );
};

This is generally much simpler to write than code that manually calls onSnapshot.

For use cases that don't fit with the useFirestore hooks, you can get a reference to the db object directly in a hook:

import { useFirebase } from "@shared/fb/firebase.store";

const useCustomHook = () => {
  const db = useFirebase("db");
};

Note that older code may use import { db } from "~/lib/firebase";. This is a deprecated pattern, as it doesn't mesh well with shared components, SSR, testing, or Storybook.

Non-react code

For non-react code, the fb/query.ts helpers have the same logic as the useFirestore hooks. You can get a reference to the FirebaseCollections through

import { getFirebase } from "@shared/fb/firebase.store";
import { watchDoc } from "@shared/db/query";

const db = getFirebase("db");
watchDoc(db.artPieces, artPiece.id, (artPiece) => console.log(artPiece));

Security Rules

Firestore security rules are stored in firestore.rules

Indexed Queries

In firestore, any query that uses more than one where or orderBy clause needs an index. There is a limit of 200 indexes per database, and each index adds some overhead to record creation, so it's important to limit the number of indexes.

Indexes are defined in `firestore.indexes.json`. When adding a new query, check to see if there is already an index for that combination of clauses. If there is not one, you should decide if you can get away with using an existing index and just filtering out some values on the client side. If not, then add a new index to the indexes file before merging. Queries without an index will work in local development, but not when deployed, so this is a manual check.

Warning on removing indexes

If you remove an index, then the next build will fail, as removing an index (or a cloud function) needs an interactive confirmation. You can manually deploy with yarn firebase deploy --only firestore to deploy the index updates interactively.

API

The API is a trusted environment that runs without Firestore security rules. Queries in API should start with AdminFirestoreCollections, which holds API-specific versions of the typed collection references.

A configured version of the collections can be imported and used like so:

import { fb } from "~/lib/fb";

const ref = fb.db.artPieces.doc(artPieceId);

Last updated