Firebase released its version 9 Web SDK around last year September. The new version implements a modular approach instead of the traditional dot-chained namespace approach. This is a major upgrade and it does require changes in the application code. This modular approach of the library allows tree shaking, such that your app imports only the services and functions it needs. Thus, reducing the build size a lot.
I was having a react project where I was using firebase v8 to create a Music streaming web app, kinda like Spotify or Youtube Music. And I did upgrade my project from firebase v8 to v9. So in this article, I’ve explained the upgrade process briefly.
Octave
Octave is a project which I created last year, just to learn about React and Firebase (version 8). It’s a Music streaming app where users can login, listen to music, create their custom playlists, and so on. I did use Firebase authentication to login users, Firestore database to store data, and Cloud storage to store mp3 files and images. I was developing this project on and off. And after a period of 6 few months, I managed to add quite a few features like searching, media session controls, and song playlist. The web app is hosted live and the code is available on GitHub.
Structure of the App
Since this is a midsize project I know that importing firebase functions directly inside components would make a mess. So, I kept all the code related to firebase under a specific directory called api. Thus, I only have to refactor the files which are in api directory. The firebase app and service instances were initialized and exported from src/firebase.js.
If you want to refer to the project, the code before updating to version 9 is available in the before-v9 branch, and the code after updating to version 9 is available in the master branch.
Installing firebase v9
npm i firebase@latest
After installing, we have to update the imports. Since I was using Firestore database, Cloud storage, authentication, I can’t update all the functions to use version 9 immediately. The updating process must be done in smaller steps, such that the app doesn’t break.
Firebase provided a solution to this transition, by introducing a compat
sub module with in firebase v9. compat
is just the same as firebase v8, such that we keep using v8 code even after installing v9, basically allowing developers to upgrade without changing all the Firebase code at once. This can be done just by changing the import statements to use the compat
version.
// version 8import firebase from "firebase/app";import "firebase/auth";import "firebase/firestore";import "firebase/storage";// version 9 compatimport firebase from "firebase/compat/app";import "firebase/compat/auth";import "firebase/compat/firestore";import "firebase/compat/storage";
After changing to compat
the app should work just fine, and we can start updating it.
Firebase mentioned that
compat
library is a temporary solution which can be used until your app is updated to v9 andcompat
will be removed in the next major release of firebase SDK.
Refactoring Authentication functions
In version 8, all the authentication methods like createUserWithEmailAndPassword, signInWithEmailAndPassword, onAuthStateChanged are provided by the auth instance. So methods are called from auth object using the dot operator. The code would look something as shown below
import "firebase/compat/auth";const auth = firebase.auth();export const signUp = (email, password) => {return auth.createUserWithEmailAndPassword(email, password);};export const signIn = (email, password) => {return auth.signInWithEmailAndPassword(email, password);};export const handleAuthStateChanged = (cb) => {return auth.onAuthStateChanged(cb);};
In v9, all the authentication methods are exported from firebase/auth
. We can import those functions into our app and start using them by simply calling them by passing the auth instance and the appropriate parameters.
import {signInWithEmailAndPassword,createUserWithEmailAndPassword,onAuthStateChanged,} from "firebase/auth";import "firebase/compat/auth";const auth = firebase.auth();export const signUp = (email, password) => {return createUserWithEmailAndPassword(auth, email, password);};export const signIn = (email, password) => {return signInWithEmailAndPassword(auth, email, password);};export const handleAuthStateChanged = (cb) => {return onAuthStateChanged(auth, cb);};
After refactoring all the authentication code to use firebase v9, we can change the auth import from firebase/compat/auth
to firebase/auth
. And auth instance can be received by calling the getAuth
function from firebase/auth
.
...import { getAuth } from "firebase/auth"......export const auth = getAuth()
In my project, I kept all Authentication functions in api/auth.js and then imported that into other components. Here is the code for before and after version 9.
Refactoring Firestore functions
Adding a Document
To add a document in version 8, you would use the db
instance and call the collection
method to select the collection within which you are going to add a document. And then you chain the add method and pass in the object which needs to be added to the collection.
Whereas in version 9 first, we have to import the functions addDoc
, collection
. we first call the collection
function passing the db
object and name of the collection that we are accessing. And then we call addDoc
function and pass the collection object and the new object which we going to insert into the collection.
// version 8 or version 9 compatexport const createNewPlaylist = (name, uid) => {return db.collection("playlists").add({uid,name,});};// version 9import { collection, adddoc } from "firebase/firestore";export const createNewPlaylist = (name, uid) => {return addDoc(collection(db, "playlists"), {uid,name,});};
Deleting a Document
In version 8 to delete a document, you would use the db
instance and call the collection
method to select the collection within which you are going to delete a document. And then you chain the delete
method and pass in the document id which needs to be deleted from the collection.
But, in version 9 first, we have to import the functions addDoc
, collection
. we first call the collection
function passing the db
object and name of the collection that we are accessing. And then we call addDoc
function and pass the collection object and the new object which we going to insert into the collection.
// version 8 or version 9 compatexport const deletePlaylist = (playlistId) => {return db.collection("playlists").doc(playlistId).delete();};// version 9import { deleteDoc, doc } from "firebase/firestore";export const deletePlaylist = (playlistId) => {return deleteDoc(doc(db, "playlists", playlistId));};
Retrieving a document
To retrieve a single document, in version 8 we would use the collection
method to select the collection and then chain the doc
method by passing the id and then chaining the get
method.
In version 9, To retrieve a single document we can use the getDoc
function by passing the document reference to it. And, to create a document reference we use the doc
function and pass the db
instance, collection name, document id.
// version 8 or version 9 copmatexport const getPlaylist = (id) => {return db.collection("playlists").doc(id).get();};// version 9import { doc, getDoc } from "firebase/firestore";export const getPlaylist = (id) => {return getDoc(doc(db, "playlists", id));};
Real Time listener
To set up a real-time listener, in version 8 you would chain the onSnapshot
method to collection
method, which can also include where
and orderBy
methods to filter and order data. We would also pass a callback function to onSnapshot
, such that every time the data changes(that is, when a document is added, removed, or modified) in the Firestore database, the callback function is invoked while passing the changed documents as arguments.
// version 8 or version 9 copmatexport const getAllPlaylists = (uid, cb) => {return db.collection("playlists").where("uid", "==", uid).orderBy("createdAt", "desc").onSnapshot(cb);};
In version 9, we use the query
function to create a query. we use the collection
function to specify what collection we are trying to use. And additionally, we can also use the where
method to specify any condition and orderBy
function for sorting data. Finally, we pass the query object to the onSnapshot
function along with the callback function, which gets called every time the query results change.
// version 9export const getAllPlaylists = (uid, cb) => {const q = query(collection(db, "playlists"),where("uid", "==", uid),orderBy("createdAt", "desc"));return onSnapshot(q, cb);};
After refactoring all the firestore code to use firebase v9, we can remove the import firebase/compat/firestore
. The db
instance can be received by calling the getFirestore
function from firebase/firestore
.
...import { getFirestore } from "firebase/firestore"......export const db = getFirestore()
Refactoring Cloud storage functions
To upload or download the file, we need to first create a reference. A reference or ref is a pointer to a file in the cloud. By default the reference points to the root of the Cloud Storage bucket, but we change that by passing the location as an argument.
In version 8, the reference is created by calling ref
method from the storage object. To upload a file the put
method is called from the reference object, where as to get the download url of a file the getDownloadURL
method is called.
// version 8 or version 9 compatexport const uploadSong = (file) => {const { name } = file;return storage.ref(`songs/${name}`).put(file);};export const getSongURL = (fileName) => {return storage.ref("songs", fileName).getDownloadURL();};
In version 9, a reference to a file can be created by calling the ref
function by passing the storage instance as an argument. To upload a file we use the uploadBytesResumable
function by passing the reference object and the file as arguments. And to get the download URL of a file, a file reference is created using the ref
function, then that reference object is passed to the getDownloadURL
function.
// version 9import { ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";export const uploadSong = (file) => {const { name } = file;const storageRef = ref(storage, `songs/${name}`);return uploadBytesResumable(storageRef, file);};export const getSongURL = (filename) => {const fileRef = ref(storage, `songs/${filename}`);return getDownloadURL(fileRef);};
After refactoring all the cloud storage code to use firebase v9, we can remove the import firebase/compat/storage
. The storage
instance can be received by calling the getStorage
function from firebase/firestore
.
...import { getStorage } from "firebase/storage"......export const storage = getStorage()
Refactoring initialization code
After updating all the service related code to version 9, finally we can update the firebase initialization code. Remove the firebase import from firebase/compat/app
and import initializeApp
from firebase/app
, and call the function passing firebase config object as a argument.
import { initializeApp } from "firebase/app"...const firebaseApp = initializeApp(firebaseConfig)...
Conclusion
Firebase is one such technology that I enjoy using, and version 9 is definitely a game changer. I hope this article gave you a brief idea about firebase version 9 and how to start updating an app that uses the older version.