I’m not very experienced in Firebase. Recently I’ve been doing stuff with Next.js + Firebase and stumbled upon this line of necessary code:
const app = !getApps().length ? initializeApp(config) : getApp()
From my understanding, this prevents multiple Firebase apps with the same config from being created. But, first of all, where does this come from? And second, how does the getApps() function know about all other apps that are not DEFAULT? Is its return mutable or read-only? How does that getApp() function (with no "s" in the end) even know which app is my default to return it, I don’t pass anything to it…
I could find nothing about this nor in the Firebase docs nor from their main speakers like David East, Todd Kerpelman, Frank van Puffelen. I know, Firebase docs are literally worst on the planet, Mario games’ are much better, but even then…
Help 🙂
2
Answers
There is something like this in a Firebase SDK:
Firebase JS SDK is written in TypeScript.
In your code, you don’t need
const app = ...
just abuse all Firebase functions. FunctiongetFirestore()
will get you a Firebase instance you need to work on, same asgetApp()
. And you canenableIndexedDbPersistence(getFirebase())
so you will cache data locally in client browser and no need any Redux/Pinia/Vuex solutions. This will reduce DB queries if you use for exampleonSnapshot()
listener. Or usegetDocFromCache()
combined withgetDoc()
.Edit:
Well, if you forget how OOP work and start to think in functional/structural programming, it’s starts to be obvious how does it work. Your app is encapsulated, but you have a "getters" "setters" to be able to work on it. Much better and easier to understand then OOP. There are no any design patterns to learn. And the library can be easily tree shakable by compilers/bundlers, so it’s lightweight.
Building on the answer by @Mises, I can provide some additional context.
As part of the built in protections to help developers avoid mistakes and race conditions,
initializeApp()
will throw an error if called twice for the same application name (where not giving a name uses"[DEFAULT]"
instead). It was also designed this way because it’s easier to just throw an error instead of comparing the configuration objects passed into eachinitializeApp()
call against the previous one. Because of this behavior,initializeApp()
should be called in your application just once, either at the top of the current file or in some central dependency (e.g.app.js
). Then when you need it, you can bring it into the current file usinggetApp()
,getFirestore()
, and so on.The
getApp()
andgetApps()
functions are part of a feature of the Firebase SDKs where you can use multiple projects in one application. The use of this feature is documented here.Loading the Firebase Dependency
For some developers, Firebase is quite the heavy dependency (especially with the legacy JavaScript SDK). So its understandable that they wouldn’t want to load it in unnecessarily. This is particularly important for web-based applications where time-to-interactivity is important or when trying to optimize cold-start times for Cloud Functions for Firebase for the best response times.
In this older video on optimizing cold-start times by @doug-stevenson, Doug covered how to use a Boolean flag to indicate whether the Firebase Admin SDK was initialized or not. This allowed a function that doesn’t use the Admin SDK to skip loading it and return a result faster.
Some developers don’t like littering their global scope with such flags, so they looked for a just-in-time alternative. This took the form of checking the length of
firebase.apps
in the legacy JavaScript SDK andadmin.apps
in the Admin SDK.The same approach worked in the client-side JavaScript SDK too:
For single-project apps, this quickly became a de-facto standard for checking if the default application was initialized, leading to the following lines turning up everywhere (especially when using one-component-per-file frameworks):
or
Summary / TL:DR;
With the move to a modular Firebase JavaScript SDK, both for
"firebase"
and"firebase-admin"
, developers and newcomers working with legacy code are updating it by following the modular SDK migration guide.This leads to the following legacy code:
being translated one-to-one to this modern code:
The primary purpose of this line is to get a properly initialized instance of the
FirebaseApp
class without throwing an error, that you can pass to the entry point functions of Firebase services included in the SDKs such as Analytics and Cloud Firestore.A Peek Under the Hood
To see how the default application instance is handballed between services in the SDK, you can take a look at the source code. The
FirebaseApp
-related functions are implemented similar to the following code.Note: I’ve omitted some validation and renamed some variables to keep it concise, you should check out the full source or look at the API reference for details.
Each service available in the SDK has an entry point function. In the legacy namespaced SDKs this took the form of
firebase.firestore()
and the modern modular SDKs usegetFirestore()
instead. Each of these entry point functions follow a similar strategy and look similar to the below code.Note: As before, this is a simplified version. See the full source and API reference for details.