Claude
Skills
Sign in
Back

firebase

Included with Lifetime
$97 forever

Firebase Firestore, Auth, Storage, real-time listeners, security rules

Security

What this skill does


# Firebase Skill


Firebase/Firestore patterns for web and mobile applications with real-time data, offline support, and security rules.

**Sources:** [Firebase Docs](https://firebase.google.com/docs) | [Firestore Best Practices](https://firebase.google.com/docs/firestore/best-practices) | [Security Rules](https://firebase.google.com/docs/rules)

---

## Core Principle

**Denormalize with purpose, secure with rules, scale horizontally.**

Firestore is a document database - embrace denormalization for read efficiency. Security rules are your server-side validation. Design for your access patterns.

---

## Firebase Stack

| Service | Purpose |
|---------|---------|
| **Firestore** | NoSQL document database with real-time sync |
| **Authentication** | User auth, OAuth, anonymous sessions |
| **Storage** | File uploads with security rules |
| **Functions** | Serverless backend (Node.js) |
| **Hosting** | Static site + CDN |
| **Extensions** | Pre-built solutions (Stripe, Algolia, etc.) |

---

## Project Setup

### Install Firebase CLI
```bash
# Install globally
npm install -g firebase-tools

# Login
firebase login

# Initialize in project
firebase init
```

### Initialize with Emulators
```bash
firebase init emulators

# Start local development
firebase emulators:start
```

### Project Structure
```
project/
├── firebase.json           # Firebase config
├── firestore.rules         # Security rules
├── firestore.indexes.json  # Composite indexes
├── storage.rules           # Storage security rules
└── functions/              # Cloud Functions
    ├── src/
    ├── package.json
    └── tsconfig.json
```

---

## Firestore Data Modeling

### Document Structure
```typescript
// Good: Flat documents with all needed data
interface Post {
  id: string;
  title: string;
  content: string;
  authorId: string;
  authorName: string;      // Denormalized for display
  authorAvatar: string;    // Denormalized
  tags: string[];
  likeCount: number;       // Aggregated counter
  createdAt: Timestamp;
  updatedAt: Timestamp;
}

// Collection: posts/{postId}
```

### When to Use Subcollections
```typescript
// Use subcollections for:
// 1. Unbounded lists (comments, messages)
// 2. Data with different access patterns
// 3. Data that grows independently

// posts/{postId}/comments/{commentId}
interface Comment {
  id: string;
  text: string;
  authorId: string;
  authorName: string;
  createdAt: Timestamp;
}
```

### Data Model Patterns

```typescript
// Pattern 1: Embedded data (bounded, always needed)
interface User {
  id: string;
  email: string;
  profile: {
    displayName: string;
    bio: string;
    avatar: string;
  };
  settings: {
    notifications: boolean;
    theme: 'light' | 'dark';
  };
}

// Pattern 2: Reference with denormalization
interface Order {
  id: string;
  userId: string;
  userEmail: string;  // Denormalized for display
  items: OrderItem[]; // Embedded (bounded)
  total: number;
  status: 'pending' | 'paid' | 'shipped';
}

// Pattern 3: Aggregation documents
// Keep counters in parent document
interface Channel {
  id: string;
  name: string;
  memberCount: number;  // Updated via Cloud Function
  messageCount: number;
}
```

---

## TypeScript SDK (Modular v9+)

### Initialize Firebase
```typescript
// lib/firebase.ts
import { initializeApp, getApps } from 'firebase/app';
import { getFirestore, connectFirestoreEmulator } from 'firebase/firestore';
import { getAuth, connectAuthEmulator } from 'firebase/auth';
import { getStorage, connectStorageEmulator } from 'firebase/storage';

const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID
};

// Initialize only once
const app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];

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

// Connect to emulators in development
if (process.env.NODE_ENV === 'development') {
  connectFirestoreEmulator(db, 'localhost', 8080);
  connectAuthEmulator(auth, 'http://localhost:9099');
  connectStorageEmulator(storage, 'localhost', 9199);
}
```

### CRUD Operations
```typescript
import {
  collection,
  doc,
  getDoc,
  getDocs,
  addDoc,
  setDoc,
  updateDoc,
  deleteDoc,
  query,
  where,
  orderBy,
  limit,
  startAfter,
  serverTimestamp,
  Timestamp
} from 'firebase/firestore';
import { db } from './firebase';

// Create
async function createPost(data: Omit<Post, 'id' | 'createdAt' | 'updatedAt'>) {
  const docRef = await addDoc(collection(db, 'posts'), {
    ...data,
    createdAt: serverTimestamp(),
    updatedAt: serverTimestamp()
  });
  return docRef.id;
}

// Read single document
async function getPost(postId: string): Promise<Post | null> {
  const docSnap = await getDoc(doc(db, 'posts', postId));
  if (!docSnap.exists()) return null;
  return { id: docSnap.id, ...docSnap.data() } as Post;
}

// Query with filters
async function getPostsByAuthor(authorId: string, pageSize = 10) {
  const q = query(
    collection(db, 'posts'),
    where('authorId', '==', authorId),
    orderBy('createdAt', 'desc'),
    limit(pageSize)
  );
  const snapshot = await getDocs(q);
  return snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() } as Post));
}

// Pagination
async function getNextPage(lastDoc: Post, pageSize = 10) {
  const q = query(
    collection(db, 'posts'),
    orderBy('createdAt', 'desc'),
    startAfter(lastDoc.createdAt),
    limit(pageSize)
  );
  const snapshot = await getDocs(q);
  return snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() } as Post));
}

// Update
async function updatePost(postId: string, data: Partial<Post>) {
  await updateDoc(doc(db, 'posts', postId), {
    ...data,
    updatedAt: serverTimestamp()
  });
}

// Delete
async function deletePost(postId: string) {
  await deleteDoc(doc(db, 'posts', postId));
}
```

### Real-time Listeners
```typescript
import { onSnapshot, QuerySnapshot, DocumentSnapshot } from 'firebase/firestore';

// Listen to single document
function subscribeToPost(
  postId: string,
  onData: (post: Post | null) => void,
  onError: (error: Error) => void
) {
  return onSnapshot(
    doc(db, 'posts', postId),
    (snapshot: DocumentSnapshot) => {
      if (!snapshot.exists()) {
        onData(null);
        return;
      }
      onData({ id: snapshot.id, ...snapshot.data() } as Post);
    },
    onError
  );
}

// Listen to collection with query
function subscribeToPosts(
  authorId: string,
  onData: (posts: Post[]) => void,
  onError: (error: Error) => void
) {
  const q = query(
    collection(db, 'posts'),
    where('authorId', '==', authorId),
    orderBy('createdAt', 'desc')
  );

  return onSnapshot(
    q,
    (snapshot: QuerySnapshot) => {
      const posts = snapshot.docs.map(doc => ({
        id: doc.id,
        ...doc.data()
      } as Post));
      onData(posts);
    },
    onError
  );
}

// React hook example
function usePost(postId: string) {
  const [post, setPost] = useState<Post | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    const unsubscribe = subscribeToPost(
      postId,
      (data) => {
        setPost(data);
        setLoading(false);
      },
      (err) => {
        setError(err);
        setLoading(false);
      }
    );
    return unsubscribe;
  }, [postId]);

  return { post, loading, error };
}
```

### Offline Persistence (Web)
```typescript
import { enableIndexedDbPersistence, enableMultiTabIndexedDbPersistence } from 'firebase/firestore';

// Enable offline persistence (call once at startup)
async function enableOffline() {
  try {
    // Single tab
    await enableIndexed
Files: 1
Size: 17.9 KB
Complexity: 25/100
Category: Security

Related in Security