firebase
firebase is the official client SDK for Google's Firebase platform: one npm package that wraps 20+ scoped @firebase/* modules covering Auth, Firestore, Realtime Database, Cloud Storage, Cloud Functions calls, Analytics, Messaging, Remote Config, App Check, and the newer AI and Data Connect products. You wire your web or Node app straight to Google-hosted backends: users sign in, documents sync in realtime, files upload, all without writing a server. Since v9 the API is modular (import individual functions) so bundlers can tree-shake the services you do not use. It is the client half of a platform; the services themselves live in your Firebase project on Google Cloud.
Unbeatable time-to-first-feature for realtime, mobile-plus-web apps, and the SDK itself is actively maintained by Google. Go in with eyes open: you are marrying a proprietary platform, and the document model plus per-read billing punish schema mistakes later.
Use it if
- You want auth, a realtime database, file storage, and hosted functions without building or operating any backend, and shipping fast matters more than infrastructure control
- You need realtime sync across clients with offline persistence; Firestore's onSnapshot listeners and local cache are genuinely hard to replicate yourself
- You are on mobile plus web; the same Firebase project serves iOS, Android, and JS SDKs with one auth and data layer
- Your usage fits the free Spark tier or predictable low volume, where the managed platform is effectively free
- You ever might migrate off; your data model, auth, and security rules are shaped around Google's proprietary services, and there is no self-hosted Firestore, so the exit cost is a rewrite
- You need relational queries; Firestore has no joins and limited aggregation, so query patterns you did not plan document structure for become client-side merges or duplicated data
- Bundle size matters and you only need one thing; even with tree-shaking, Firestore alone commonly adds a few hundred KB to a bundle, far beyond a plain fetch-based API client
- Your costs scale with reads; Firestore bills per document read, and a badly structured listener or missing pagination can turn a traffic spike into a real bill
- You want SQL and open-source portability; Supabase gives you Postgres with auth and realtime and you can take the database anywhere
Setup reality
npm install firebase, then paste the config object from the Firebase console into initializeApp. Every service needs console-side setup first: enable each sign-in provider by hand, create the Firestore database and pick a region you can never change, write security rules in Google's own rules language (the default locked mode makes every request fail until you do). The v9+ modular API means long import lists from firebase/auth and firebase/firestore, and most Stack Overflow answers still show the pre-v9 namespaced style, so half the snippets you find need translating. Local development against the Emulator Suite requires installing firebase-tools separately and a Java runtime. SSR frameworks need care: Analytics and Messaging are browser-only and crash when imported in Node.
Patterns
Initialize the app onceinit-app
import { initializeApp } from 'firebase/app'
const app = initializeApp({
apiKey: '...',
authDomain: 'my-app.firebaseapp.com',
projectId: 'my-app',
storageBucket: 'my-app.appspot.com',
appId: '...'
})The apiKey is a project identifier, not a secret; access control lives in security rules, which start locked and deny everything.
Email/password sign-inemail-sign-in
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth'
const auth = getAuth(app)
const cred = await signInWithEmailAndPassword(auth, email, password)
console.log(cred.user.uid)Fails with auth/operation-not-allowed until you enable the Email/Password provider in the console's Authentication tab.
React to sign-in state changesauth-state
import { getAuth, onAuthStateChanged } from 'firebase/auth'
const unsubscribe = onAuthStateChanged(getAuth(app), (user) => {
if (user) showApp(user)
else showLogin()
})Auth state restores asynchronously on page load, so currentUser is null at startup even for signed-in users; always wait for this callback.
Create or overwrite a documentfirestore-write
import { getFirestore, doc, setDoc, collection, addDoc } from 'firebase/firestore'
const db = getFirestore(app)
await setDoc(doc(db, 'users', uid), { name: 'Ada', plan: 'free' })
const ref = await addDoc(collection(db, 'posts'), { title: 'Hi', ts: Date.now() })setDoc replaces the whole document; pass { merge: true } or use updateDoc to change individual fields.
Read a single documentfirestore-read
import { doc, getDoc } from 'firebase/firestore'
const snap = await getDoc(doc(db, 'users', uid))
if (snap.exists()) {
console.log(snap.data())
}A missing document is not an error; getDoc resolves normally and exists() is false, so check it before calling data().
Query a collection with filtersfirestore-query
import { collection, query, where, orderBy, limit, getDocs } from 'firebase/firestore'
const q = query(
collection(db, 'posts'),
where('authorId', '==', uid),
orderBy('ts', 'desc'),
limit(20)
)
const snap = await getDocs(q)
snap.forEach((d) => console.log(d.id, d.data()))Combining where and orderBy on different fields needs a composite index; the thrown error includes a console link that creates it for you.
Subscribe to live document updatesrealtime-listener
import { doc, onSnapshot } from 'firebase/firestore'
const unsubscribe = onSnapshot(doc(db, 'games', gameId), (snap) => {
render(snap.data())
})
// later: unsubscribe()Every listener re-read bills as document reads; forgetting to unsubscribe in SPA route changes leaks listeners and money.
Upload a file and get its URLstorage-upload
import { getStorage, ref, uploadBytes, getDownloadURL } from 'firebase/storage'
const storage = getStorage(app)
const fileRef = ref(storage, `avatars/${uid}.png`)
await uploadBytes(fileRef, file)
const url = await getDownloadURL(fileRef)Storage has its own security rules separate from Firestore's; default rules block uploads from unauthenticated users.
Call a Cloud Functioncallable-function
import { getFunctions, httpsCallable } from 'firebase/functions'
const functions = getFunctions(app)
const createInvite = httpsCallable(functions, 'createInvite')
const { data } = await createInvite({ email: 'x@y.com' })Callables pass auth context automatically; if your function is deployed outside us-central1, pass the region to getFunctions or you get 404s.
Develop against the local Emulator Suitelocal-emulators
import { connectAuthEmulator } from 'firebase/auth'
import { connectFirestoreEmulator } from 'firebase/firestore'
if (location.hostname === 'localhost') {
connectAuthEmulator(auth, 'http://127.0.0.1:9099')
connectFirestoreEmulator(db, '127.0.0.1', 8080)
}Emulators come from the separate firebase-tools CLI and need Java installed; connect calls must run before any other SDK operation.
Enable offline cache for Firestoreoffline-persistence
import { initializeFirestore, persistentLocalCache, persistentMultipleTabManager } from 'firebase/firestore'
const db = initializeFirestore(app, {
localCache: persistentLocalCache({
tabManager: persistentMultipleTabManager()
})
})Use initializeFirestore instead of getFirestore when configuring the cache; the older enableIndexedDbPersistence API is deprecated.
Write a trustworthy server-side timestampserver-timestamp
import { doc, updateDoc, serverTimestamp } from 'firebase/firestore'
await updateDoc(doc(db, 'posts', id), {
updatedAt: serverTimestamp()
})Client clocks lie; in latency-compensated local snapshots the field is briefly null before the server value arrives.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| @supabase/supabase-js | npm | You want the same auth-plus-data-plus-realtime bundle on Postgres with an open-source, portable core |
| aws-amplify | npm | Your organization is on AWS and you need Cognito, AppSync, and S3 as the managed backend |
| pocketbase | npm | You want a small self-hosted backend (auth, realtime, files) in a single binary with a JS client |