Type-proofing my Firestore with 🦎 Salamander

Nick Felker
3 min readSep 8, 2022

--

Salamanders and fire have mythical connections

I’ve got this spare project, a game, that I’ve been working on in the little spare time I have. I’ve mentioned it before. The backend is entirely Firebase: using Cloud Functions, Hosting, and user data stored in Cloud Firestore.

Cloud Firestore is a fast and reliable NoSQL product to store all kinds of data. There’s no schemas, so it’s very easy to add all kinds of data to documents. However, this has at times been a problem.

Admittedly, I have wiped out some player data when I use

user.set({ /* data */ })

rather than

user.update({ /* data */ })

This is because the CloudFirestore get, set, and update methods don’t care about types. They’ll happily accept anything because it doesn’t require a strict schema.

But in order to fix my mistakes and ensure I’m correctly changing user data, I have wrapped the Cloud Firestore methods in a small library called Salamander.

Using the Salamander

After importing the salamander function, you just execute it by passing in the standard admin.firestore() object.

Then you can use generics and an API just like standard Cloud Firestore. You pass in User as the type to the get method and that will become the type of doc.

But you could already just use casting. What makes this better?

It’s better than it comes to write operations. Consider a case where I want to update the value of my User.

The code above actually will throw an error now. Because Salamander knows the type is User , and lastName is not defined in my operation, it won’t let me run this code. With Cloud Firestore normally, it would be fine doing that, potentially deleting all of the last names in my database.

VSCode shows me a swell error now

This actually happened to me! So now at least I’m protecting myself from myself.

When doing an update, not every field needs to be present, but the types of the values are still validated and provides just a bit more reassurance.

Even for simple types with string-based values, I can be sure that my types match and the transpiler will not build at all until everything matches.

These type-safe method calls also are implemented for transactions.

Conclusion

The code for this is very small. In fact, you can see the entire file on GitHub. Most of the repository is documentation and configurations. Still, it adds just a bit more reliability to what I’m doing.

--

--

Nick Felker

Social Media Expert -- Rowan University 2017 -- IoT & Assistant @ Google