Learn-skills.dev api-baas-firebase

Firebase backend-as-a-service — Firestore, Authentication, Cloud Functions v2, Storage, Hosting, Admin SDK, security rules, emulator suite

install
source · Clone the upstream repo
git clone https://github.com/NeverSight/learn-skills.dev
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/agents-inc/skills/api-baas-firebase" ~/.claude/skills/neversight-learn-skills-dev-api-baas-firebase && rm -rf "$T"
manifest: data/skills-md/agents-inc/skills/api-baas-firebase/SKILL.md
source content

Firebase Patterns

Quick Guide: Use Firebase as your backend-as-a-service for Firestore database, authentication, Cloud Functions, file storage, and hosting. Always use the modular SDK (

firebase/app
,
firebase/firestore
, etc.) for tree-shaking, type Firestore documents with TypeScript interfaces, write security rules for every collection, and use the Admin SDK only on the server.


<critical_requirements>

CRITICAL: Before Using This Skill

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,

import type
, named constants)

(You MUST use the modular Firebase SDK imports (

firebase/app
,
firebase/firestore
,
firebase/auth
) -- NEVER use the deprecated
firebase/compat
namespace API)

(You MUST write Firestore security rules for EVERY collection -- a collection without rules is wide open in production)

(You MUST NEVER expose Firebase Admin SDK credentials or service account keys in client-side code)

(You MUST use Cloud Functions v2 API (

firebase-functions/v2/https
,
firebase-functions/v2/firestore
) -- NOT the deprecated v1 API)

(You MUST handle all Firestore operations with error checking -- never assume reads/writes succeed)

</critical_requirements>


Auto-detection: Firebase, initializeApp, firebase/app, firebase/firestore, firebase/auth, getFirestore, getAuth, onAuthStateChanged, collection, doc, getDocs, setDoc, updateDoc, deleteDoc, onSnapshot, firebase-admin, firebase-functions, Cloud Functions, Firestore security rules, firebase.json, firebase deploy

When to use:

  • Initializing Firebase and configuring services (Firestore, Auth, Storage, Functions)
  • Implementing authentication (email/password, OAuth, phone, custom tokens)
  • Querying and writing Firestore documents (CRUD, real-time listeners, transactions)
  • Writing Cloud Functions v2 (HTTP handlers, callable functions, Firestore triggers, scheduled functions)
  • Uploading and serving files from Firebase Storage
  • Deploying to Firebase Hosting with function rewrites
  • Writing Firestore and Storage security rules
  • Using the Firebase Admin SDK for server-side operations

Key patterns covered:

  • Modular SDK setup with
    initializeApp
    and service getters (
    getFirestore
    ,
    getAuth
    ,
    getStorage
    )
  • Auth flows: sign up, sign in, OAuth, phone auth,
    onAuthStateChanged
    , session management
  • Firestore CRUD with
    doc()
    ,
    collection()
    ,
    getDocs()
    ,
    setDoc()
    ,
    updateDoc()
    ,
    deleteDoc()
  • Firestore real-time listeners with
    onSnapshot()
  • Firestore queries with
    where()
    ,
    orderBy()
    ,
    limit()
    , composite indexes
  • Cloud Functions v2:
    onRequest
    ,
    onCall
    ,
    onDocumentCreated
    ,
    onSchedule
  • Firebase Admin SDK:
    initializeApp()
    ,
    getFirestore()
    ,
    getAuth()
    , custom tokens, user management
  • Security rules: read/write granularity, auth-based access, data validation
  • Emulator suite for local development and testing
  • Offline persistence with
    persistentLocalCache

When NOT to use:

  • Complex relational queries needing JOIN operations (use a relational database with an ORM)
  • Full server-side ORM patterns (Firestore is a document database, not relational)
  • Applications using a non-Firebase authentication provider
  • Applications requiring complex server-side business logic beyond Cloud Functions scope

Examples:


<philosophy>

Philosophy

Firebase is Google's Backend-as-a-Service platform providing a complete backend through Firestore (document database), Authentication, Cloud Functions, Storage, Hosting, and more. The modular SDK (v12+) uses tree-shakeable ES module imports for minimal bundle sizes.

Core principles:

  1. Modular imports for tree-shaking -- Import only what you need from
    firebase/firestore
    ,
    firebase/auth
    , etc. The modular SDK can reduce bundle size by 80%+ compared to the legacy namespace API.
  2. Document-oriented data model -- Firestore stores data as documents in collections. Design your data model around your query patterns, not normalized relations. Denormalization is expected.
  3. Security rules are mandatory -- Firestore and Storage are directly accessible from clients. Security rules are your only server-side access control. Every collection needs rules.
  4. Cloud Functions v2 on Cloud Run -- 2nd generation functions run on Cloud Run with better scaling, concurrency, longer timeouts (up to 60 minutes), and traffic splitting. Always use v2 for new projects.
  5. Offline-first with persistence -- Firestore supports offline persistence via IndexedDB. Enable it with
    persistentLocalCache
    for apps that must work without connectivity.
  6. Admin SDK for server-side -- The Firebase Admin SDK bypasses security rules and has full access. Use it only in trusted server environments (Cloud Functions, API servers).

When to use Firebase:

  • Rapid prototyping and MVPs with auth, database, and storage out of the box
  • Real-time applications (chat, live dashboards, collaborative editing) via Firestore listeners
  • Mobile and web apps needing offline support with automatic sync
  • Projects wanting serverless backend logic with Cloud Functions
  • Applications needing simple file storage with security rules

When NOT to use:

  • Complex relational data with many-to-many relationships and JOINs (use a relational database)
  • Applications needing full-text search (Firestore has limited query capabilities -- use a dedicated search service)
  • High-write-throughput scenarios exceeding 10,000 writes/second to a single document
  • Applications requiring complex server-side transactions spanning multiple services
</philosophy>
<patterns>

Core Patterns

Pattern 1: Firebase App Initialization (Modular SDK)

Initialize Firebase with the modular SDK for tree-shaking. Each service has its own import path.

// lib/firebase.ts
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import { getAuth } from "firebase/auth";
import { getStorage } from "firebase/storage";

const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);
export const auth = getAuth(app);
export const storage = getStorage(app);

Why good: Modular imports enable tree-shaking, each service initialized from the app instance, named exports

Full setup with emulators and offline persistence: examples/core.md


Pattern 2: Firestore CRUD Operations

Use modular functions for all Firestore read/write operations. Always type your documents.

const POSTS_COLLECTION = "posts";

// Read
const docSnap = await getDoc(doc(db, POSTS_COLLECTION, postId));
if (!docSnap.exists()) throw new Error(`Not found: ${postId}`);

// Create with auto-ID and server timestamp
const docRef = doc(collection(db, POSTS_COLLECTION));
await setDoc(docRef, { ...data, createdAt: serverTimestamp() });

// Update specific fields
await updateDoc(doc(db, POSTS_COLLECTION, postId), {
  ...data,
  updatedAt: serverTimestamp(),
});

// Delete
await deleteDoc(doc(db, POSTS_COLLECTION, postId));

Why good: Named constants for collection names, existence check before accessing data,

serverTimestamp()
for consistent timestamps, typed input parameters

Full CRUD service with pagination: examples/firestore.md


Pattern 3: Firestore Real-Time Listeners

Subscribe to document and collection changes with

onSnapshot
. Always unsubscribe to prevent memory leaks.

function subscribeToPublishedPosts(
  onUpdate: (posts: Post[]) => void,
  onError: (error: Error) => void,
): Unsubscribe {
  const q = query(
    collection(db, POSTS_COLLECTION),
    where("published", "==", true),
    orderBy("createdAt", "desc"),
  );
  return onSnapshot(
    q,
    (snapshot) => {
      onUpdate(
        snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }) as Post),
      );
    },
    (error) => {
      onError(new Error(`Listener failed: ${error.message}`));
    },
  );
}

// Always store and call the unsubscribe function
const unsubscribe = subscribeToPublishedPosts(handler, errorHandler);
unsubscribe(); // Cleanup when done

Why good: Returns

Unsubscribe
for cleanup, separate error callback, typed parameters

Real-time chat implementation: examples/firestore.md


Pattern 4: Firestore Transactions and Batched Writes

Use transactions for atomic read-then-write operations. Use batched writes for multiple writes without reads.

// Transaction: atomic read-then-write
await runTransaction(db, async (transaction) => {
  const postSnap = await transaction.get(postRef);
  if (!postSnap.exists()) throw new Error("Not found");
  transaction.update(postRef, { likeCount: increment(1) });
  transaction.set(likeRef, { createdAt: serverTimestamp() });
});

// Batch: multiple writes (up to 500)
const batch = writeBatch(db);
for (const id of postIds.slice(0, MAX_BATCH_SIZE)) {
  batch.delete(doc(db, POSTS_COLLECTION, id));
}
await batch.commit();

Why good: Transaction ensures atomicity,

increment()
for safe counters, batch for bulk operations

Full examples: examples/firestore.md


Pattern 5: Authentication Flows

Use Firebase Authentication with the modular SDK. Handle auth state changes with

onAuthStateChanged
.

// Sign up / Sign in / Sign out
const credential = await createUserWithEmailAndPassword(auth, email, password);
const credential = await signInWithEmailAndPassword(auth, email, password);
await signOut(auth);

// OAuth
const result = await signInWithPopup(auth, new GoogleAuthProvider());

// Listen to auth state -- register early in app lifecycle
const unsubscribe = onAuthStateChanged(auth, (user) => {
  /* ... */
});

// Get ID token for API calls
const token = await auth.currentUser?.getIdToken(/* forceRefresh */ true);

Why good: Modular imports, typed

User
return values,
Unsubscribe
for cleanup

Full auth service with profile sync: examples/auth.md


Pattern 6: Cloud Functions v2

Write Cloud Functions using the v2 API. Import from

firebase-functions/v2/*
subpackages.

// HTTP function
import { onRequest } from "firebase-functions/v2/https";
export const getPosts = onRequest(
  { cors: true, region: "us-central1" },
  async (req, res) => {
    /* ... */
  },
);

// Callable function (with auth)
import { onCall, HttpsError } from "firebase-functions/v2/https";
export const createPost = onCall({ region: "us-central1" }, async (request) => {
  if (!request.auth)
    throw new HttpsError("unauthenticated", "Must be signed in");
  /* ... */
});

// Firestore trigger
import { onDocumentCreated } from "firebase-functions/v2/firestore";
export const onPostCreated = onDocumentCreated(
  { document: "posts/{postId}", region: "us-central1" },
  async (event) => {
    /* ... */
  },
);

// Scheduled function
import { onSchedule } from "firebase-functions/v2/scheduler";
export const cleanup = onSchedule(
  { schedule: "every 24 hours", region: "us-central1" },
  async () => {
    /* ... */
  },
);

Why good: v2 imports from subpackages, region specified,

HttpsError
for callable error handling

Full Cloud Functions API: examples/functions.md


Pattern 7: Firebase Admin SDK (Server-Side)

Use the Admin SDK in Cloud Functions or trusted server environments. Bypasses all security rules.

import { initializeApp } from "firebase-admin/app";
import { getFirestore } from "firebase-admin/firestore";
import { getAuth } from "firebase-admin/auth";

initializeApp(); // Default credentials in Cloud Functions
export const adminDb = getFirestore();
export const adminAuth = getAuth();

// Custom claims for role-based access
await adminAuth.setCustomUserClaims(uid, { admin: true });
// Verify client ID tokens
const decoded = await adminAuth.verifyIdToken(idToken);

Why good: Modular Admin SDK imports, default credentials in Cloud Functions, custom claims for RBAC

Full Admin SDK patterns: examples/functions.md


Pattern 8: Firebase Storage

Upload, download, and manage files with Firebase Storage.

import { ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";

const storageRef = ref(storage, `avatars/${userId}/avatar.png`);
const uploadTask = uploadBytesResumable(storageRef, file, {
  contentType: file.type,
  customMetadata: { uploadedBy: userId },
});

uploadTask.on("state_changed", (snapshot) => {
  const PERCENT_MULTIPLIER = 100;
  const percent =
    (snapshot.bytesTransferred / snapshot.totalBytes) * PERCENT_MULTIPLIER;
  onProgress(percent);
});

Why good: Resumable upload with progress,

customMetadata
for audit trail, named constants

Full upload/download/delete with validation: examples/storage.md


Pattern 9: Security Rules

Firestore and Storage security rules are your primary access control mechanism.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    function isAuthenticated() { return request.auth != null; }
    function isOwner(userId) { return request.auth.uid == userId; }

    match /posts/{postId} {
      allow read: if resource.data.published == true
                  || isOwner(resource.data.authorId);
      allow create: if isAuthenticated()
                    && request.resource.data.authorId == request.auth.uid;
      allow update: if isOwner(resource.data.authorId);
      allow delete: if isOwner(resource.data.authorId);
    }
  }
}

Why good: Helper functions, granular CRUD permissions, ownership checks, data validation

Full Firestore + Storage rules: examples/security-rules.md

</patterns>
<performance>

Performance Optimization

Query Performance

  • Use composite indexes -- Compound queries (multiple
    where
    +
    orderBy
    ) need composite indexes. Deploy via
    firestore.indexes.json
    or let Firestore error messages guide you with auto-generated index links.
  • Limit result sets -- Always use
    limit()
    to cap query results. Unbounded queries fetch all matching documents.
  • Paginate with cursors -- Use
    startAfter()
    with the last document snapshot for efficient pagination instead of
    offset()
    .
  • Select specific fields -- Use
    select()
    in Admin SDK queries to fetch only needed fields (client SDK always fetches full documents).

Bundle Size

  • Use
    initializeAuth
    for granular control
    --
    getAuth()
    enables all auth methods by default. Use
    initializeAuth()
    with only the providers you need for smaller bundles.
  • Use
    firebase/firestore/lite
    -- If you don't need real-time listeners, import from
    firebase/firestore/lite
    for a smaller Firestore bundle (no
    onSnapshot
    ).

Cloud Functions

  • Minimize cold starts -- Use
    onInit()
    for lazy initialization. Keep function dependencies small. Consider "fat functions" (one entry point routing to handlers) over many small functions.
  • Set appropriate memory/timeout -- Configure
    memory
    and
    timeoutSeconds
    in function options instead of using defaults.
  • Use global variables for reusable connections -- Initialize Admin SDK and database connections outside the function handler so they persist across invocations.
</performance>

<decision_framework>

Decision Framework

Firestore vs Realtime Database

What does your app need?
+-- Complex queries (where, orderBy, compound) --> Firestore
+-- Simple key-value lookups with low latency --> Realtime Database
+-- Offline support with rich queries --> Firestore
+-- Presence system (online/offline status) --> Realtime Database
+-- Multi-region availability --> Firestore
+-- Very frequent small updates (typing indicators) --> Realtime Database
+-- For most new projects --> Firestore (recommended default)

Auth Method Selection

What auth flow does the user need?
+-- Email + Password --> createUserWithEmailAndPassword / signInWithEmailAndPassword
+-- Social login (Google, GitHub, etc.) --> signInWithPopup / signInWithRedirect
+-- Phone + SMS --> signInWithPhoneNumber (requires reCAPTCHA)
+-- Email link (passwordless) --> sendSignInLinkToEmail
+-- Custom backend auth --> Admin SDK createCustomToken + client signInWithCustomToken
+-- Anonymous (guest) --> signInAnonymously (upgrade later with linkWithCredential)

Cloud Functions: onRequest vs onCall

Who is calling the function?
+-- External webhooks, third-party services --> onRequest (raw HTTP)
+-- Your own client app (web/mobile) --> onCall (automatic auth, input validation)
+-- Firestore document changes --> onDocumentCreated / onDocumentUpdated / onDocumentDeleted
+-- Scheduled/cron tasks --> onSchedule
+-- Authentication events --> onUserCreated / onUserDeleted (from firebase-functions/v2/identity)

Client SDK vs Admin SDK

Where is the code running?
+-- Browser / Client-side --> Client SDK (firebase) -- security rules enforced
+-- Cloud Functions --> Admin SDK (firebase-admin) -- bypasses security rules
+-- API server / backend --> Admin SDK (firebase-admin) -- use service account
+-- NEVER use Admin SDK in client code --> It bypasses all security

Storage: When to Use Firebase Storage

What kind of files?
+-- User-generated content (avatars, uploads) --> Firebase Storage with security rules
+-- Public static assets (CSS, images) --> Firebase Hosting (faster CDN)
+-- Large files with progress tracking --> Firebase Storage with uploadBytesResumable
+-- Server-generated files (reports, exports) --> Firebase Storage via Admin SDK

</decision_framework>


<integration>

Integration Guide

Firebase ecosystem integration:

  • Firebase Hosting + Cloud Functions: Rewrite rules in
    firebase.json
    route requests to functions
  • App Check: Protect your backend resources from abuse by verifying requests come from your app (
    initializeAppCheck
    with reCAPTCHA Enterprise)
  • Firebase Extensions: Pre-built Cloud Functions for common tasks (payments, image resizing, email sending)

Client framework integration:

  • Use
    onAuthStateChanged
    in your framework's lifecycle (e.g., context provider, composable, store) to track auth state
  • Firestore
    onSnapshot
    listeners require cleanup when components unmount -- use your framework's cleanup mechanism
  • Wrap Firestore reads in your data fetching solution's query functions for caching and real-time invalidation

Replaces / Conflicts with:

  • Other BaaS platforms -- don't use two BaaS solutions for the same purpose
  • Custom auth providers -- Firebase Auth can replace third-party auth, or vice versa -- don't mix auth providers
</integration>

<red_flags>

RED FLAGS

High Priority Issues:

  • Wide-open security rules in production -- The default test-mode rules (
    allow read, write: if true
    ) give everyone full access to your entire database. This is the single most common Firebase security vulnerability.
  • Admin SDK credentials in client code -- Service account keys or Admin SDK imports in browser bundles give attackers full bypass of all security rules.
  • Using the compat/namespace API --
    firebase/compat/*
    imports prevent tree-shaking, bundling the entire SDK. The compat layer will be removed in a future major version.
  • Not handling auth state changes -- Calling Firestore without waiting for
    onAuthStateChanged
    can result in unauthenticated requests that fail silently against security rules.

Medium Priority Issues:

  • Using v1 Cloud Functions API -- v1 (
    functions.https.onRequest
    ) lacks concurrency, traffic splitting, and longer timeouts. All new functions should use v2 (
    firebase-functions/v2/https
    ).
  • Not unsubscribing from
    onSnapshot
    -- Leaked listeners keep WebSocket connections open, consume bandwidth, and cause memory leaks.
  • Unbounded queries -- Queries without
    limit()
    can fetch thousands of documents, causing performance issues and high read costs.
  • Monotonically increasing document IDs -- Sequential IDs like
    user1
    ,
    user2
    create hotspots. Use Firestore auto-generated IDs or UUIDs.
  • Using
    functions.config()
    -- Deprecated and will fail after March 2027. Use Cloud Secret Manager (
    defineSecret()
    ) or environment variables.

Common Mistakes:

  • Not adding
    .select()
    after Admin SDK queries
    -- Without
    select()
    , all document fields are returned, increasing data transfer.
  • Calling
    getFirestore()
    on every operation
    -- Initialize once and reuse the instance. Each call creates overhead.
  • Missing composite indexes -- Compound queries fail at runtime if the required composite index doesn't exist. Check error messages for the auto-generated index creation link.
  • Deploying without testing security rules -- Use the emulator to test rules before deploying. Use
    firebase emulators:exec "npm test"
    in CI.
  • Using
    enableIndexedDbPersistence
    -- Deprecated. Use
    initializeFirestore
    with
    persistentLocalCache
    instead.

Gotchas & Edge Cases:

  • Firestore queries are "all or nothing" with security rules -- If a query could potentially return documents the user isn't allowed to read, the entire query fails (not just the unauthorized documents).
  • Firestore limits -- 1 MB max document size, 20,000 fields per document, 500 writes per batch, 1 write per second per document sustained.
  • Security rules propagation delay -- Rule updates take up to 1 minute to affect new queries, and up to 10 minutes for active listeners.
  • serverTimestamp()
    returns
    null
    in
    onSnapshot
    pending writes
    -- Until the server confirms the write, the timestamp field is
    null
    locally. Handle this with
    { serverTimestamps: 'estimate' }
    in snapshot options.
  • Firebase Hosting has a 60-second timeout -- Even if your Cloud Function has a longer timeout, requests through Hosting rewrites timeout at 60 seconds.
  • onAuthStateChanged
    fires on page load
    -- It fires with
    null
    initially, then with the user if a session exists. Always handle the initial
    null
    state.
  • Firestore
    in
    queries are limited to 30 values
    --
    where("field", "in", array)
    supports a maximum of 30 elements in the array (increased from 10 in recent versions).
  • Cloud Functions cold starts -- First invocation after idle has additional latency. Use
    onInit()
    for lazy initialization and keep dependencies minimal.
  • Admin SDK
    initializeApp()
    should be called once
    -- Multiple calls throw an error unless you provide a unique app name. Guard with a try-catch or check
    getApps().length
    .

</red_flags>


<critical_reminders>

CRITICAL REMINDERS

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,

import type
, named constants)

(You MUST use the modular Firebase SDK imports (

firebase/app
,
firebase/firestore
,
firebase/auth
) -- NEVER use the deprecated
firebase/compat
namespace API)

(You MUST write Firestore security rules for EVERY collection -- a collection without rules is wide open in production)

(You MUST NEVER expose Firebase Admin SDK credentials or service account keys in client-side code)

(You MUST use Cloud Functions v2 API (

firebase-functions/v2/https
,
firebase-functions/v2/firestore
) -- NOT the deprecated v1 API)

(You MUST handle all Firestore operations with error checking -- never assume reads/writes succeed)

Failure to follow these rules will create security vulnerabilities, bloated bundles, deprecated code paths, and silent runtime failures.

</critical_reminders>