Can I force a Firestore document to have a specific Type or data model (i.e. static typing)?

Viewed 103

For example, if I have a Firestore document that looks like this:

{
  threadId: 'abc',
  authorId: 'abc',
  content: 'blah blah blahh'
}

Can I make Firebase throw an error if I accidentally write some code that adds another field, like this:

{
  accident: 'OOPS! THIS FIELD SHOULD NOT BE HERE',
  threadId: 'abc',
  authorId: 'abc',
  content: 'blah blah blahh'
}

Like static typing for the shape of each document and enforced from the Firestore side. Is that possible?

3 Answers

A possible solution would be to create a Cloud Function, that reads the content of your document once it is updated. So for example, if your document has a fixed number of fields, then you should always check against that number, in your example, it would be three. If for example, the result of the check would be four, then you can take some actions.

Another approach would be to check the existence of a field in a document, before updating it. If that particular field exists, do the update, otherwise, throw an error.

If you write the data from a client-side SDK, you can use security rules to validate the data. For some examples of this, see the Firestore documentation on validating data in security rules.

For example, to test that the document has only the three fields you mention, you could do:

allow write: if request.resource.data.keys.hasOnly("threadId", "authorId", "content")

What you actually need is to use FirestoreDataConverter. You can transform your own objects of type <T> to Firestore data by using the withConverter() method. Also you will have your static typed objects (documents) while retrieving them from Firestore. All you need is to implement two methods toFirestore() and fromFirestore()

  1. Let's say you have:
class Thread {
  constructor(threadId: String, authorId: String, content: String) {}
}
  1. Define a converter:
const threadsConverter = {
  toFirestore(thread: Thread): DocumentData {
    return { threadId: thread.threadId, authorId: thread.authoId, content: thread.content }
  },
  fromFirestore(snapshot: QueryDocumentSnapshot): Thread {
    const data = snapshot.data();
    const thread = new Thread(data.threadId, data.authorId, data.content);
    return thread
  }
}
  1. Use it:
   // write:
   const myObject = {
     accident: 'OOPS! THIS FIELD SHOULD NOT BE HERE',
     threadId: 'abc',
     authorId: 'abc',
     content: 'blah blah blahh'
   }
   const threadRef = firestore.collection("thread").withConverter(threadsConverter).doc()
   await threadRef.set(myObject);
   
   // read:
   const threads = await firestore.collection("threads").withConverter(threadsConverter).get();

And that's it. Hope it helps.

P.S. Didn't test the code.

Related