Today I Learned

How to type-safely interact with Firestore documents

The problem

When using Typescript and Firestore, we usually have to do a lot of manual casting when working with documents. One such example would be getting the data of a document:

const thread = threadDocument.data(); // this will be of type any

Should we want to interact with the data in a type-safe manner, we'll have to cast it, which can quickly become tedious.

const thread = <ThreadData>threadDocument.data();

Additionally, when we write data to Firestore, there are no restrictions on how the data should look.

The solution

This is when Firestore Data Converters can come in handy. All we have to do is implement two methods - one where we constrain the data that gets written and one where we cast the data coming from Firestore:

const converter = {
  toFirestore: (dataToBeWritten: ThreadData) => data,
  fromFirestore: (document: QueryDocumentSnapshot) => <ThreadData>document.data(),

To take this one step further, we can store the "converted" collection reference so we won't have to apply the converters each time we query the collection:

const threadCollection = db.collection("threads").withConverter(converter);

Now we can safely interact with the collection without having to cast the data:

const threadDocument = await threadCollection.doc(id).get();
const thread = threadDocument.data(); // this will be of type ThreadData