Treat your Firebase Database like a giant Redux Store
Firebase server events + Firebase authentication = Redux-like action handling for fun and profit
I was recently asked about patterns I use when using Firebase with React and NodeJS (no, I really was!)
If you’re reading this, you probably know Redux and the elegance of its unidirectional data flow. Given that both a Redux store and the Firebase database are essentially just a monolithic object, I’ve used (okay, stole) the conceptual ideas of Redux to set up a secure and predictable way of updating my global Firebase dataset.
- I allow write access to one key only in my Firebase database: action
- New data written to action gets “signed” with a Firebase authentication auth.uid
- A node listener receives new actions via Firebase server events
- The listener acts like a Redux root reducer to update the global dataset
- … which then emits server events to get picked up by websockety websites and apps the world over
And that’s it.
Firebase authentication is your no-code friend
My root Firebase database authentication rule is this:
No one can read anything and no one can write anything. As this is a bit too restrictive, I do allow some reads, like:
".read": "$uid === auth.uid"
Authenticated users can read (but not write) their own user details. I also use values stored in /users/[uid] to determine what else they can have read access to. Consider this:
".read": "auth !== null && root.child('users/'+auth.uid+'/can_read/protected_thing').val() === true"
So unless the Firebase database contains
/users/[uid]/can_read/protected_thing = true
user uid cannot access protected_thing. With zero code written I have secured access to that part of the database!
Extending this to every Firebase key in the database, all websites and apps can access any bit they see fit, and I can sleep soundly knowing no-one can get at anything they’re not supposed to. GDPR? I’m, like, there.
Make with the Redux already
And now, for the first Reduxy bit — action dispatching, Firebase style.
There’s only one key in my whole database that users have write access to: action. Note that it can’t however, be read…
".write": "(auth !== null && auth.uid === newData.child('uid').val()"
The auth.uid === newData.child(‘uid’).val() is important — it means that the data a user writes to action must have a uid child having the value of their auth.uid.
In this way, every action gets signed by the user that created it, and, again gets enforced with zero code. OAuths? Token passing? Private keys and encryption? Pah! Not for us Firebasers…
In a non-Reduxy fashion, my action listener might write a response. Here’s my authentication rules for action_response
".read": "(auth !== null && $uid === auth.uid)",
".write": "(auth !== null && $uid === auth.uid && !newData.exists())"
Note that users can not just read, but also write to keys under their own auth.uid — but the !newData.exists() means that all they can write is null: users are allowed to delete responses once they’ve handled them.
The node listener/root reducer is simple: it’s just a function that responds to a child_added to action, in full knowledge that the data contains the uid of the user that posted it.
This listener/reducer can then use the current state of the database and whatever logic it needs to update the database to a new state: and the subscribing websites and apps will receive those updates with Firebase server event magic.
Syncing your Firebase Database with a Redux Store
Like the listener/reducer , keeping a Redux store in sync with Firebase is fairly straightforward: if you have functions that dispatch a value to a Redux store, then you just need to subscribe to the appropriate Firebase ref, and dispatch to Redux whenever you get a child_added, child_changed or child_removed on the relevant Firebase key.
Here’s a gist for a function that does just that: https://gist.github.com/christianchown/b995249d09d5b17d9827230294118d52 (I really should middleware or thunk this: oh well)
And here’s a complementary gist that pushes actions and retrieves the responses: https://gist.github.com/christianchown/4945bc02a6b57642868d52592f4b3e86