firebase-firestore

安装量: 433
排名: #2320

安装

npx skills add https://github.com/jezweb/claude-skills --skill firebase-firestore

Firebase Firestore Database

Status: Production Ready Last Updated: 2026-01-25 Dependencies: None (standalone skill) Latest Versions: firebase@12.8.0, firebase-admin@13.6.0

Quick Start (5 Minutes) 1. Install Firebase SDK

Client SDK (web/mobile)

npm install firebase

Admin SDK (server/backend)

npm install firebase-admin

  1. Initialize Firebase (Client) // src/lib/firebase.ts import { initializeApp } from 'firebase/app'; import { getFirestore } from 'firebase/firestore';

const firebaseConfig = { apiKey: process.env.FIREBASE_API_KEY, authDomain: process.env.FIREBASE_AUTH_DOMAIN, projectId: process.env.FIREBASE_PROJECT_ID, storageBucket: process.env.FIREBASE_STORAGE_BUCKET, messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID, appId: process.env.FIREBASE_APP_ID, };

const app = initializeApp(firebaseConfig); export const db = getFirestore(app);

  1. Initialize Firebase Admin (Server) // src/lib/firebase-admin.ts import { initializeApp, cert, getApps } from 'firebase-admin/app'; import { getFirestore } from 'firebase-admin/firestore';

// Initialize only once if (!getApps().length) { initializeApp({ credential: cert({ projectId: process.env.FIREBASE_PROJECT_ID, clientEmail: process.env.FIREBASE_CLIENT_EMAIL, // Replace escaped newlines in private key privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\n/g, '\n'), }), }); }

export const adminDb = getFirestore();

CRITICAL:

Never expose FIREBASE_PRIVATE_KEY in client code Use Admin SDK for server-side operations (bypasses security rules) Use Client SDK for authenticated user operations Core Operations Document CRUD (Client SDK - Modular v9+) import { collection, doc, addDoc, getDoc, getDocs, setDoc, updateDoc, deleteDoc, query, where, orderBy, limit, serverTimestamp, Timestamp, } from 'firebase/firestore'; import { db } from './firebase';

// CREATE - Auto-generated ID const docRef = await addDoc(collection(db, 'users'), { name: 'John Doe', email: 'john@example.com', createdAt: serverTimestamp(), }); console.log('Created document with ID:', docRef.id);

// CREATE - Specific ID await setDoc(doc(db, 'users', 'user-123'), { name: 'Jane Doe', email: 'jane@example.com', createdAt: serverTimestamp(), });

// READ - Single document const docSnap = await getDoc(doc(db, 'users', 'user-123')); if (docSnap.exists()) { console.log('Document data:', docSnap.data()); } else { console.log('No such document!'); }

// READ - Collection with query const q = query( collection(db, 'users'), where('email', '==', 'john@example.com'), orderBy('createdAt', 'desc'), limit(10) ); const querySnapshot = await getDocs(q); querySnapshot.forEach((doc) => { console.log(doc.id, ' => ', doc.data()); });

// UPDATE - Merge fields (doesn't overwrite entire document) await updateDoc(doc(db, 'users', 'user-123'), { name: 'Jane Smith', updatedAt: serverTimestamp(), });

// UPDATE - Set with merge (creates if doesn't exist) await setDoc(doc(db, 'users', 'user-123'), { lastLogin: serverTimestamp(), }, { merge: true });

// DELETE await deleteDoc(doc(db, 'users', 'user-123'));

Document CRUD (Admin SDK) import { adminDb } from './firebase-admin'; import { FieldValue, Timestamp } from 'firebase-admin/firestore';

// CREATE const docRef = await adminDb.collection('users').add({ name: 'John Doe', createdAt: FieldValue.serverTimestamp(), });

// CREATE with specific ID await adminDb.collection('users').doc('user-123').set({ name: 'Jane Doe', createdAt: FieldValue.serverTimestamp(), });

// READ const doc = await adminDb.collection('users').doc('user-123').get(); if (doc.exists) { console.log('Document data:', doc.data()); }

// READ with query const snapshot = await adminDb .collection('users') .where('email', '==', 'john@example.com') .orderBy('createdAt', 'desc') .limit(10) .get();

snapshot.forEach((doc) => { console.log(doc.id, '=>', doc.data()); });

// UPDATE await adminDb.collection('users').doc('user-123').update({ name: 'Jane Smith', updatedAt: FieldValue.serverTimestamp(), });

// DELETE await adminDb.collection('users').doc('user-123').delete();

Real-Time Listeners (Client SDK) import { onSnapshot, query, where, collection, doc } from 'firebase/firestore'; import { db } from './firebase';

// Listen to single document const unsubscribe = onSnapshot(doc(db, 'users', 'user-123'), (doc) => { if (doc.exists()) { console.log('Current data:', doc.data()); } });

// Listen to collection with query const q = query( collection(db, 'messages'), where('roomId', '==', 'room-123'), orderBy('createdAt', 'desc'), limit(50) );

const unsubscribeMessages = onSnapshot(q, (querySnapshot) => { const messages: Message[] = []; querySnapshot.forEach((doc) => { messages.push({ id: doc.id, ...doc.data() } as Message); }); // Update UI with messages setMessages(messages); });

// Handle errors const unsubscribeWithError = onSnapshot( doc(db, 'users', 'user-123'), (doc) => { // Handle updates }, (error) => { console.error('Listener error:', error); // Handle permission denied, etc. } );

// IMPORTANT: Unsubscribe when done (React useEffect cleanup) useEffect(() => { const unsubscribe = onSnapshot(/ ... /); return () => unsubscribe(); }, []);

CRITICAL:

Always unsubscribe from listeners to prevent memory leaks Listeners count against your concurrent connection limit Each listener is a WebSocket connection Query Patterns Compound Queries import { query, where, orderBy, limit, startAfter, collection } from 'firebase/firestore';

// Multiple where clauses (requires composite index) const q = query( collection(db, 'products'), where('category', '==', 'electronics'), where('price', '<=', 1000), orderBy('price', 'asc') );

// Range query (only one field can have inequality) const rangeQuery = query( collection(db, 'events'), where('date', '>=', new Date('2025-01-01')), where('date', '<=', new Date('2025-12-31')), orderBy('date', 'asc') );

// Array contains const arrayQuery = query( collection(db, 'posts'), where('tags', 'array-contains', 'firebase') );

// Array contains any (max 30 values) const arrayAnyQuery = query( collection(db, 'posts'), where('tags', 'array-contains-any', ['firebase', 'google', 'cloud']) );

// In query (max 30 values) const inQuery = query( collection(db, 'users'), where('status', 'in', ['active', 'pending']) );

// Not in query (max 10 values) const notInQuery = query( collection(db, 'users'), where('status', 'not-in', ['banned', 'deleted']) );

Pagination with Cursors import { query, orderBy, limit, startAfter, getDocs, collection, DocumentSnapshot } from 'firebase/firestore';

let lastVisible: DocumentSnapshot | null = null;

async function getNextPage() { let q = query( collection(db, 'posts'), orderBy('createdAt', 'desc'), limit(10) );

if (lastVisible) { q = query(q, startAfter(lastVisible)); }

const snapshot = await getDocs(q);

// Save last document for next page lastVisible = snapshot.docs[snapshot.docs.length - 1] || null;

return snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); }

Collection Group Queries import { collectionGroup, query, where, getDocs } from 'firebase/firestore';

// Query across all subcollections named 'comments' // Structure: posts/{postId}/comments/{commentId} const q = query( collectionGroup(db, 'comments'), where('authorId', '==', 'user-123') );

const snapshot = await getDocs(q); // Returns all comments by user-123 across all posts

CRITICAL: Collection group queries require an index. Create in Firebase Console or deploy via firestore.indexes.json.

Batch Operations & Transactions Batch Writes (Up to 500 operations) import { writeBatch, doc, collection, serverTimestamp } from 'firebase/firestore'; import { db } from './firebase';

const batch = writeBatch(db);

// Add multiple documents const usersRef = collection(db, 'users'); batch.set(doc(usersRef), { name: 'User 1', createdAt: serverTimestamp() }); batch.set(doc(usersRef), { name: 'User 2', createdAt: serverTimestamp() });

// Update existing document batch.update(doc(db, 'counters', 'users'), { total: 100 });

// Delete document batch.delete(doc(db, 'temp', 'old-doc'));

// Commit all operations atomically await batch.commit();

Transactions (Read then Write) import { runTransaction, doc, increment } from 'firebase/firestore'; import { db } from './firebase';

// Transfer credits between users async function transferCredits(fromId: string, toId: string, amount: number) { await runTransaction(db, async (transaction) => { const fromRef = doc(db, 'users', fromId); const toRef = doc(db, 'users', toId);

const fromDoc = await transaction.get(fromRef);
const toDoc = await transaction.get(toRef);

if (!fromDoc.exists() || !toDoc.exists()) {
  throw new Error('User not found');
}

const fromCredits = fromDoc.data().credits;
if (fromCredits < amount) {
  throw new Error('Insufficient credits');
}

transaction.update(fromRef, { credits: fromCredits - amount });
transaction.update(toRef, { credits: increment(amount) });

}); }

CRITICAL:

Transactions can fail and retry automatically (up to 5 times) Don't perform side effects inside transaction (may run multiple times) All reads must come before writes in a transaction Security Rules Basic Rules Structure // firestore.rules rules_version = '2';

service cloud.firestore { match /databases/{database}/documents {

// Helper functions
function isAuthenticated() {
  return request.auth != null;
}

function isOwner(userId) {
  return request.auth.uid == userId;
}

function isValidUser() {
  return request.resource.data.keys().hasAll(['name', 'email'])
    && request.resource.data.name is string
    && request.resource.data.email is string;
}

// Users collection
match /users/{userId} {
  allow read: if isAuthenticated();
  allow create: if isAuthenticated() && isOwner(userId) && isValidUser();
  allow update: if isOwner(userId);
  allow delete: if isOwner(userId);
}

// Posts collection with subcollections
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, delete: if isOwner(resource.data.authorId);

  // Comments subcollection
  match /comments/{commentId} {
    allow read: if true;
    allow create: if isAuthenticated();
    allow update, delete: if isOwner(resource.data.authorId);
  }
}

// Admin-only collection
match /admin/{document=**} {
  allow read, write: if request.auth.token.admin == true;
}

} }

Deploy Rules

Deploy rules

firebase deploy --only firestore:rules

Deploy rules and indexes

firebase deploy --only firestore

Indexes Composite Indexes (firestore.indexes.json) { "indexes": [ { "collectionGroup": "products", "queryScope": "COLLECTION", "fields": [ { "fieldPath": "category", "order": "ASCENDING" }, { "fieldPath": "price", "order": "ASCENDING" } ] }, { "collectionGroup": "comments", "queryScope": "COLLECTION_GROUP", "fields": [ { "fieldPath": "authorId", "order": "ASCENDING" }, { "fieldPath": "createdAt", "order": "DESCENDING" } ] } ], "fieldOverrides": [] }

Deploy indexes

firebase deploy --only firestore:indexes

CRITICAL:

Firestore auto-creates single-field indexes Composite indexes must be created manually or via error link Collection group queries always require an index Offline Persistence Enable Offline Support (Web) import { initializeFirestore, persistentLocalCache, persistentMultipleTabManager } from 'firebase/firestore'; import { app } from './firebase';

// Enable multi-tab offline persistence const db = initializeFirestore(app, { localCache: persistentLocalCache({ tabManager: persistentMultipleTabManager() }) });

// OR: Enable single-tab persistence (simpler) import { enableIndexedDbPersistence, getFirestore } from 'firebase/firestore';

const db = getFirestore(app); enableIndexedDbPersistence(db).catch((err) => { if (err.code === 'failed-precondition') { // Multiple tabs open, persistence can only be enabled in one tab console.warn('Persistence failed: multiple tabs open'); } else if (err.code === 'unimplemented') { // Browser doesn't support persistence console.warn('Persistence not supported'); } });

Handle Offline State import { onSnapshot, doc, SnapshotMetadata } from 'firebase/firestore';

onSnapshot(doc(db, 'users', 'user-123'), (doc) => { const source = doc.metadata.fromCache ? 'local cache' : 'server'; console.log(Data came from ${source});

if (doc.metadata.hasPendingWrites) { console.log('Local changes pending sync'); } });

Data Modeling Best Practices Denormalization for Read Performance // Instead of joining users and posts... // Store author info directly in post document

// posts/{postId} { title: 'My Post', content: '...', authorId: 'user-123', // Denormalized author data for fast reads author: { name: 'John Doe', avatarUrl: 'https://...' }, createdAt: Timestamp }

Subcollections vs Root Collections // Subcollections: Good for parent-child relationships // posts/{postId}/comments/{commentId} // - Easy to query all comments for a post // - Deleting post doesn't auto-delete comments (use Cloud Functions)

// Root collections: Good for cross-cutting queries // comments (with postId field) // - Easy to query all comments by a user across posts // - Requires manual data consistency

Counter Pattern (High-Write Scenarios) // Direct increment (low traffic) await updateDoc(doc(db, 'posts', postId), { viewCount: increment(1) });

// Distributed counter (high traffic - 1000+ writes/sec) // Use Cloud Functions to aggregate shard counts // counters/{counterId}/shards/{shardId}

Error Handling Common Errors and Solutions Error Cause Solution permission-denied Security rules blocking access Check rules, ensure user authenticated not-found Document doesn't exist Use exists() check before accessing data already-exists Document with ID already exists Use setDoc with merge or generate new ID resource-exhausted Quota exceeded Upgrade plan or optimize queries failed-precondition Index missing for query Create composite index (link in error) unavailable Service temporarily unavailable Implement retry with backoff invalid-argument Invalid query combination Check query constraints (see below) deadline-exceeded Operation timeout Reduce data size or paginate Query Constraints // INVALID: Multiple inequality filters on different fields query(collection(db, 'posts'), where('date', '>', startDate), where('likes', '>', 100) // ERROR: Can't use inequality on second field );

// VALID: Use range on one field, equality on others query(collection(db, 'posts'), where('category', '==', 'tech'), where('date', '>', startDate) );

// INVALID: orderBy field different from inequality field query(collection(db, 'posts'), where('date', '>', startDate), orderBy('likes') // ERROR: Must orderBy('date') first );

// VALID: orderBy inequality field first query(collection(db, 'posts'), where('date', '>', startDate), orderBy('date'), orderBy('likes') );

Known Issues Prevention

This skill prevents 10 documented Firestore errors:

Issue # Error/Issue Description How to Avoid Source

1 permission-denied Security rules blocking operation Test rules in Firebase Console emulator first Common

2 failed-precondition (index) Composite index missing Click error link to create index, or define in firestore.indexes.json Common

3 Invalid query combination Multiple inequality filters Use inequality on one field only, equality on others Docs

4 Memory leak from listeners Not unsubscribing from onSnapshot Always call unsubscribe in cleanup (useEffect return) Common

5 Offline persistence conflict Multiple tabs with persistence Use persistentMultipleTabManager() or handle error Docs

6 Transaction side effects Side effects run multiple times Never perform side effects inside runTransaction Docs

7 Batch limit exceeded More than 500 operations Split into multiple batches Docs

8 resource-exhausted Quota limits hit Implement pagination, reduce reads, use caching Common

9 Private key newline issue \n not converted in env var Use .replace(/\n/g, '\n') on private key Common

10 Collection group query fails Missing collection group index Create index with queryScope: COLLECTION_GROUP Docs

Firebase CLI Commands

Initialize Firestore

firebase init firestore

Start emulators

firebase emulators:start --only firestore

Deploy rules and indexes

firebase deploy --only firestore

Export data (for backup)

gcloud firestore export gs://your-bucket/backups/$(date +%Y%m%d)

Import data

gcloud firestore import gs://your-bucket/backups/20250125

Package Versions (Verified 2026-01-25) { "dependencies": { "firebase": "^12.8.0" }, "devDependencies": { "firebase-admin": "^13.6.0" } }

Official Documentation Firestore Overview: https://firebase.google.com/docs/firestore Get Started: https://firebase.google.com/docs/firestore/quickstart Data Model: https://firebase.google.com/docs/firestore/data-model Security Rules: https://firebase.google.com/docs/firestore/security/get-started Query Data: https://firebase.google.com/docs/firestore/query-data/queries Manage Data: https://firebase.google.com/docs/firestore/manage-data/add-data Offline Data: https://firebase.google.com/docs/firestore/manage-data/enable-offline

Last verified: 2026-01-25 | Skill version: 1.0.0

返回排行榜