firebase

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Firebase Skill

Firebase 技能指南

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

加载依赖:base.md + security.md
适用于Web和移动应用的Firebase/Firestore实践模式,涵盖实时数据、离线支持和安全规则。

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.

有目的地进行数据反规范化,通过规则保障安全,实现水平扩展。
Firestore是文档型数据库——为了读取效率应采用数据反规范化。安全规则充当你的服务端验证机制,需根据数据访问模式进行设计。

Firebase Stack

Firebase 技术栈

ServicePurpose
FirestoreNoSQL document database with real-time sync
AuthenticationUser auth, OAuth, anonymous sessions
StorageFile uploads with security rules
FunctionsServerless backend (Node.js)
HostingStatic site + CDN
ExtensionsPre-built solutions (Stripe, Algolia, etc.)

服务用途
Firestore支持实时同步的NoSQL文档型数据库
Authentication用户身份验证、OAuth授权、匿名会话
Storage带安全规则的文件上传存储
Functions无服务器后端(Node.js)
Hosting静态站点托管 + CDN
Extensions预构建解决方案(如Stripe、Algolia等)

Project Setup

项目搭建

Install Firebase CLI

安装Firebase CLI

bash
undefined
bash
undefined

Install globally

全局安装

npm install -g firebase-tools
npm install -g firebase-tools

Login

登录

firebase login
firebase login

Initialize in project

在项目中初始化

firebase init
undefined
firebase init
undefined

Initialize with Emulators

使用模拟器初始化

bash
firebase init emulators
bash
firebase init emulators

Start local development

启动本地开发环境

firebase emulators:start
undefined
firebase emulators:start
undefined

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

project/
├── firebase.json           # Firebase 配置文件
├── firestore.rules         # Firestore 安全规则
├── firestore.indexes.json  # 复合索引
├── storage.rules           # Storage 安全规则
└── functions/              # 云函数
    ├── src/
    ├── package.json
    └── tsconfig.json

Firestore Data Modeling

Firestore 数据建模

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}
typescript
// 推荐:包含所有所需数据的扁平化文档
interface Post {
  id: string;
  title: string;
  content: string;
  authorId: string;
  authorName: string;      // 反规范化用于直接展示
  authorAvatar: string;    // 反规范化
  tags: string[];
  likeCount: number;       // 聚合计数器
  createdAt: Timestamp;
  updatedAt: Timestamp;
}

// 集合路径: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;
}
typescript
// 以下场景适合使用子集合:
// 1. 无界列表(如评论、消息)
// 2. 具有不同访问模式的数据
// 3. 独立增长的数据

// 路径: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
// 模式1:嵌入式数据(数量有限、始终需要)
interface User {
  id: string;
  email: string;
  profile: {
    displayName: string;
    bio: string;
    avatar: string;
  };
  settings: {
    notifications: boolean;
    theme: 'light' | 'dark';
  };
}

// 模式2:带反规范化的引用
interface Order {
  id: string;
  userId: string;
  userEmail: string;  // 反规范化用于直接展示
  items: OrderItem[]; // 嵌入式(数量有限)
  total: number;
  status: 'pending' | 'paid' | 'shipped';
}

// 模式3:聚合文档
// 在父文档中维护计数器
interface Channel {
  id: string;
  name: string;
  memberCount: number;  // 通过云函数更新
  messageCount: number;
}

TypeScript SDK (Modular v9+)

TypeScript SDK(模块化v9+)

Initialize Firebase

初始化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);
}
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
};

// 仅初始化一次
const app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];

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

// 开发环境连接模拟器
if (process.env.NODE_ENV === 'development') {
  connectFirestoreEmulator(db, 'localhost', 8080);
  connectAuthEmulator(auth, 'http://localhost:9099');
  connectStorageEmulator(storage, 'localhost', 9199);
}

CRUD Operations

CRUD 操作

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));
}
typescript
import {
  collection,
  doc,
  getDoc,
  getDocs,
  addDoc,
  setDoc,
  updateDoc,
  deleteDoc,
  query,
  where,
  orderBy,
  limit,
  startAfter,
  serverTimestamp,
  Timestamp
} from 'firebase/firestore';
import { db } from './firebase';

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

// 读取单个文档
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;
}

// 带筛选条件的查询
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));
}

// 分页查询
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));
}

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

// 删除文档
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 };
}
typescript
import { onSnapshot, QuerySnapshot, DocumentSnapshot } from 'firebase/firestore';

// 监听单个文档
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
  );
}

// 监听带查询条件的集合
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 示例
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)

离线持久化(Web端)

typescript
import { enableIndexedDbPersistence, enableMultiTabIndexedDbPersistence } from 'firebase/firestore';

// Enable offline persistence (call once at startup)
async function enableOffline() {
  try {
    // Single tab
    await enableIndexedDbPersistence(db);

    // OR multi-tab (recommended)
    await enableMultiTabIndexedDbPersistence(db);
  } catch (err: any) {
    if (err.code === 'failed-precondition') {
      // Multiple tabs open, only works in one
      console.warn('Persistence only available in one tab');
    } else if (err.code === 'unimplemented') {
      // Browser doesn't support
      console.warn('Persistence not supported');
    }
  }
}

// Check if data is from cache
onSnapshot(docRef, (snapshot) => {
  const source = snapshot.metadata.fromCache ? 'cache' : 'server';
  console.log(`Data from ${source}`);

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

typescript
import { enableIndexedDbPersistence, enableMultiTabIndexedDbPersistence } from 'firebase/firestore';

// 启用离线持久化(启动时调用一次)
async function enableOffline() {
  try {
    // 单标签页模式
    await enableIndexedDbPersistence(db);

    // 或多标签页模式(推荐)
    await enableMultiTabIndexedDbPersistence(db);
  } catch (err: any) {
    if (err.code === 'failed-precondition') {
      // 多个标签页已打开,仅能在一个标签页启用
      console.warn('持久化仅支持单个标签页');
    } else if (err.code === 'unimplemented') {
      // 浏览器不支持
      console.warn('当前浏览器不支持持久化');
    }
  }
}

// 检查数据是否来自缓存
onSnapshot(docRef, (snapshot) => {
  const source = snapshot.metadata.fromCache ? '缓存' : '服务器';
  console.log(`数据来源:${source}`);

  if (snapshot.metadata.hasPendingWrites) {
    console.log('本地修改待同步至服务器');
  }
});

Security Rules

安全规则

Basic Rules Structure

基础规则结构

javascript
// 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 isAdmin() {
      return request.auth.token.admin == true;
    }

    // Posts collection
    match /posts/{postId} {
      // Anyone can read published posts
      allow read: if resource.data.status == 'published';

      // Only authenticated users can create
      allow create: if isAuthenticated()
        && request.resource.data.authorId == request.auth.uid
        && request.resource.data.keys().hasAll(['title', 'content', 'authorId']);

      // Only author can update
      allow update: if isOwner(resource.data.authorId)
        && request.resource.data.authorId == resource.data.authorId; // Can't change author

      // Only author or admin can delete
      allow delete: if isOwner(resource.data.authorId) || isAdmin();

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

    // User profiles
    match /users/{userId} {
      allow read: if true;
      allow create: if isAuthenticated() && isOwner(userId);
      allow update: if isOwner(userId);
      allow delete: if false; // Never allow delete
    }

    // Private user data
    match /users/{userId}/private/{document=**} {
      allow read, write: if isOwner(userId);
    }
  }
}
javascript
// firestore.rules
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // 辅助函数
    function isAuthenticated() {
      return request.auth != null;
    }

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

    function isAdmin() {
      return request.auth.token.admin == true;
    }

    // 帖子集合
    match /posts/{postId} {
      // 任何人都可以阅读已发布的帖子
      allow read: if resource.data.status == 'published';

      // 仅已认证用户可创建帖子
      allow create: if isAuthenticated()
        && request.resource.data.authorId == request.auth.uid
        && request.resource.data.keys().hasAll(['title', 'content', 'authorId']);

      // 仅作者可更新帖子
      allow update: if isOwner(resource.data.authorId)
        && request.resource.data.authorId == resource.data.authorId; // 禁止修改作者

      // 仅作者或管理员可删除帖子
      allow delete: if isOwner(resource.data.authorId) || isAdmin();

      // 评论子集合
      match /comments/{commentId} {
        allow read: if true;
        allow create: if isAuthenticated();
        allow update, delete: if isOwner(resource.data.authorId);
      }
    }

    // 用户资料
    match /users/{userId} {
      allow read: if true;
      allow create: if isAuthenticated() && isOwner(userId);
      allow update: if isOwner(userId);
      allow delete: if false; // 禁止删除用户资料
    }

    // 用户私有数据
    match /users/{userId}/private/{document=**} {
      allow read, write: if isOwner(userId);
    }
  }
}

Data Validation in Rules

规则中的数据验证

javascript
match /posts/{postId} {
  function isValidPost() {
    let data = request.resource.data;
    return data.title is string
      && data.title.size() >= 3
      && data.title.size() <= 100
      && data.content is string
      && data.content.size() <= 50000
      && data.tags is list
      && data.tags.size() <= 5;
  }

  allow create: if isAuthenticated() && isValidPost();
  allow update: if isOwner(resource.data.authorId) && isValidPost();
}
javascript
match /posts/{postId} {
  function isValidPost() {
    let data = request.resource.data;
    return data.title is string
      && data.title.size() >= 3
      && data.title.size() <= 100
      && data.content is string
      && data.content.size() <= 50000
      && data.tags is list
      && data.tags.size() <= 5;
  }

  allow create: if isAuthenticated() && isValidPost();
  allow update: if isOwner(resource.data.authorId) && isValidPost();
}

Test Rules Locally

本地测试规则

bash
undefined
bash
undefined

Install emulators

安装模拟器

firebase emulators:start
firebase emulators:start

Run rules tests

运行规则测试

npm test

```typescript
// tests/firestore.rules.test.ts
import { assertFails, assertSucceeds, initializeTestEnvironment } from '@firebase/rules-unit-testing';

describe('Firestore Rules', () => {
  let testEnv: RulesTestEnvironment;

  beforeAll(async () => {
    testEnv = await initializeTestEnvironment({
      projectId: 'test-project',
      firestore: { rules: fs.readFileSync('firestore.rules', 'utf8') }
    });
  });

  test('unauthenticated users cannot write', async () => {
    const unauthedDb = testEnv.unauthenticatedContext().firestore();
    await assertFails(
      setDoc(doc(unauthedDb, 'posts/test'), { title: 'Test' })
    );
  });

  test('users can only update own posts', async () => {
    const aliceDb = testEnv.authenticatedContext('alice').firestore();
    const bobDb = testEnv.authenticatedContext('bob').firestore();

    // Create as Alice
    await assertSucceeds(
      setDoc(doc(aliceDb, 'posts/test'), { title: 'Test', authorId: 'alice' })
    );

    // Bob cannot update
    await assertFails(
      updateDoc(doc(bobDb, 'posts/test'), { title: 'Hacked' })
    );
  });
});

npm test

```typescript
// tests/firestore.rules.test.ts
import { assertFails, assertSucceeds, initializeTestEnvironment } from '@firebase/rules-unit-testing';

describe('Firestore Rules', () => {
  let testEnv: RulesTestEnvironment;

  beforeAll(async () => {
    testEnv = await initializeTestEnvironment({
      projectId: 'test-project',
      firestore: { rules: fs.readFileSync('firestore.rules', 'utf8') }
    });
  });

  test('未认证用户无法写入', async () => {
    const unauthedDb = testEnv.unauthenticatedContext().firestore();
    await assertFails(
      setDoc(doc(unauthedDb, 'posts/test'), { title: 'Test' })
    );
  });

  test('用户仅能更新自己的帖子', async () => {
    const aliceDb = testEnv.authenticatedContext('alice').firestore();
    const bobDb = testEnv.authenticatedContext('bob').firestore();

    // 以Alice身份创建帖子
    await assertSucceeds(
      setDoc(doc(aliceDb, 'posts/test'), { title: 'Test', authorId: 'alice' })
    );

    // Bob无法更新该帖子
    await assertFails(
      updateDoc(doc(bobDb, 'posts/test'), { title: 'Hacked' })
    );
  });
});

Authentication

身份验证

Email/Password Auth

邮箱/密码认证

typescript
import {
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  signOut,
  onAuthStateChanged,
  User
} from 'firebase/auth';
import { auth } from './firebase';

// Sign up
async function signUp(email: string, password: string) {
  const credential = await createUserWithEmailAndPassword(auth, email, password);
  return credential.user;
}

// Sign in
async function signIn(email: string, password: string) {
  const credential = await signInWithEmailAndPassword(auth, email, password);
  return credential.user;
}

// Sign out
async function logout() {
  await signOut(auth);
}

// Auth state listener
function onAuthChange(callback: (user: User | null) => void) {
  return onAuthStateChanged(auth, callback);
}
typescript
import {
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  signOut,
  onAuthStateChanged,
  User
} from 'firebase/auth';
import { auth } from './firebase';

// 注册账号
async function signUp(email: string, password: string) {
  const credential = await createUserWithEmailAndPassword(auth, email, password);
  return credential.user;
}

// 登录账号
async function signIn(email: string, password: string) {
  const credential = await signInWithEmailAndPassword(auth, email, password);
  return credential.user;
}

// 退出登录
async function logout() {
  await signOut(auth);
}

// 认证状态监听器
function onAuthChange(callback: (user: User | null) => void) {
  return onAuthStateChanged(auth, callback);
}

OAuth Providers

OAuth 第三方登录

typescript
import {
  GoogleAuthProvider,
  signInWithPopup,
  signInWithRedirect
} from 'firebase/auth';

const googleProvider = new GoogleAuthProvider();

async function signInWithGoogle() {
  try {
    const result = await signInWithPopup(auth, googleProvider);
    return result.user;
  } catch (error) {
    // Handle errors
    throw error;
  }
}

typescript
import {
  GoogleAuthProvider,
  signInWithPopup,
  signInWithRedirect
} from 'firebase/auth';

const googleProvider = new GoogleAuthProvider();

async function signInWithGoogle() {
  try {
    const result = await signInWithPopup(auth, googleProvider);
    return result.user;
  } catch (error) {
    // 错误处理
    throw error;
  }
}

Cloud Functions

云函数

Basic HTTP Function

基础HTTP函数

typescript
// functions/src/index.ts
import { onRequest } from 'firebase-functions/v2/https';
import { onDocumentCreated } from 'firebase-functions/v2/firestore';
import { initializeApp } from 'firebase-admin/app';
import { getFirestore } from 'firebase-admin/firestore';

initializeApp();
const db = getFirestore();

// HTTP endpoint
export const helloWorld = onRequest((request, response) => {
  response.json({ message: 'Hello from Firebase!' });
});

// Firestore trigger
export const onPostCreated = onDocumentCreated('posts/{postId}', async (event) => {
  const snapshot = event.data;
  if (!snapshot) return;

  const post = snapshot.data();

  // Update author's post count
  await db.doc(`users/${post.authorId}`).update({
    postCount: FieldValue.increment(1)
  });
});
typescript
// functions/src/index.ts
import { onRequest } from 'firebase-functions/v2/https';
import { onDocumentCreated } from 'firebase-functions/v2/firestore';
import { initializeApp } from 'firebase-admin/app';
import { getFirestore } from 'firebase-admin/firestore';

initializeApp();
const db = getFirestore();

// HTTP 接口
export const helloWorld = onRequest((request, response) => {
  response.json({ message: 'Hello from Firebase!' });
});

// Firestore 触发器
export const onPostCreated = onDocumentCreated('posts/{postId}', async (event) => {
  const snapshot = event.data;
  if (!snapshot) return;

  const post = snapshot.data();

  // 更新作者的帖子数量
  await db.doc(`users/${post.authorId}`).update({
    postCount: FieldValue.increment(1)
  });
});

Callable Functions

可调用函数

typescript
// Backend
import { onCall, HttpsError } from 'firebase-functions/v2/https';

export const createPost = onCall(async (request) => {
  // Auth check
  if (!request.auth) {
    throw new HttpsError('unauthenticated', 'Must be logged in');
  }

  const { title, content } = request.data;

  // Validation
  if (!title || title.length < 3) {
    throw new HttpsError('invalid-argument', 'Title must be at least 3 characters');
  }

  // Create post
  const postRef = await db.collection('posts').add({
    title,
    content,
    authorId: request.auth.uid,
    createdAt: FieldValue.serverTimestamp()
  });

  return { postId: postRef.id };
});

// Frontend
import { getFunctions, httpsCallable } from 'firebase/functions';

const functions = getFunctions();
const createPostFn = httpsCallable(functions, 'createPost');

async function createPost(title: string, content: string) {
  const result = await createPostFn({ title, content });
  return result.data as { postId: string };
}

typescript
// 后端实现
import { onCall, HttpsError } from 'firebase-functions/v2/https';

export const createPost = onCall(async (request) => {
  // 认证检查
  if (!request.auth) {
    throw new HttpsError('unauthenticated', '必须登录后操作');
  }

  const { title, content } = request.data;

  // 参数验证
  if (!title || title.length < 3) {
    throw new HttpsError('invalid-argument', '标题长度至少3个字符');
  }

  // 创建帖子
  const postRef = await db.collection('posts').add({
    title,
    content,
    authorId: request.auth.uid,
    createdAt: FieldValue.serverTimestamp()
  });

  return { postId: postRef.id };
});

// 前端调用
import { getFunctions, httpsCallable } from 'firebase/functions';

const functions = getFunctions();
const createPostFn = httpsCallable(functions, 'createPost');

async function createPost(title: string, content: string) {
  const result = await createPostFn({ title, content });
  return result.data as { postId: string };
}

Batch Operations & Transactions

批量操作与事务

Batch Writes

批量写入

typescript
import { writeBatch, doc } from 'firebase/firestore';

async function batchUpdate(updates: { id: string; data: any }[]) {
  const batch = writeBatch(db);

  updates.forEach(({ id, data }) => {
    batch.update(doc(db, 'posts', id), data);
  });

  await batch.commit(); // Atomic
}
typescript
import { writeBatch, doc } from 'firebase/firestore';

async function batchUpdate(updates: { id: string; data: any }[]) {
  const batch = writeBatch(db);

  updates.forEach(({ id, data }) => {
    batch.update(doc(db, 'posts', id), data);
  });

  await batch.commit(); // 原子操作
}

Transactions

事务

typescript
import { runTransaction, doc, increment } from 'firebase/firestore';

async function likePost(postId: string, userId: string) {
  await runTransaction(db, async (transaction) => {
    const postRef = doc(db, 'posts', postId);
    const likeRef = doc(db, 'posts', postId, 'likes', userId);

    const postSnap = await transaction.get(postRef);
    if (!postSnap.exists()) throw new Error('Post not found');

    const likeSnap = await transaction.get(likeRef);
    if (likeSnap.exists()) throw new Error('Already liked');

    transaction.set(likeRef, { createdAt: serverTimestamp() });
    transaction.update(postRef, { likeCount: increment(1) });
  });
}

typescript
import { runTransaction, doc, increment } from 'firebase/firestore';

async function likePost(postId: string, userId: string) {
  await runTransaction(db, async (transaction) => {
    const postRef = doc(db, 'posts', postId);
    const likeRef = doc(db, 'posts', postId, 'likes', userId);

    const postSnap = await transaction.get(postRef);
    if (!postSnap.exists()) throw new Error('帖子不存在');

    const likeSnap = await transaction.get(likeRef);
    if (likeSnap.exists()) throw new Error('已点赞过该帖子');

    transaction.set(likeRef, { createdAt: serverTimestamp() });
    transaction.update(postRef, { likeCount: increment(1) });
  });
}

Indexes

索引

Composite Indexes

复合索引

json
// firestore.indexes.json
{
  "indexes": [
    {
      "collectionGroup": "posts",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "authorId", "order": "ASCENDING" },
        { "fieldPath": "createdAt", "order": "DESCENDING" }
      ]
    },
    {
      "collectionGroup": "posts",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "tags", "arrayConfig": "CONTAINS" },
        { "fieldPath": "createdAt", "order": "DESCENDING" }
      ]
    }
  ]
}
bash
undefined
json
// firestore.indexes.json
{
  "indexes": [
    {
      "collectionGroup": "posts",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "authorId", "order": "ASCENDING" },
        { "fieldPath": "createdAt", "order": "DESCENDING" }
      ]
    },
    {
      "collectionGroup": "posts",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "tags", "arrayConfig": "CONTAINS" },
        { "fieldPath": "createdAt", "order": "DESCENDING" }
      ]
    }
  ]
}
bash
undefined

Deploy indexes

部署索引

firebase deploy --only firestore:indexes

---
firebase deploy --only firestore:indexes

---

CLI Quick Reference

CLI 快速参考

bash
undefined
bash
undefined

Project setup

项目设置

firebase login # Authenticate firebase init # Initialize project firebase projects:list # List projects
firebase login # 登录认证 firebase init # 初始化项目 firebase projects:list # 列出所有项目

Emulators

模拟器操作

firebase emulators:start # Start all emulators firebase emulators:start --only firestore,auth # Specific emulators
firebase emulators:start # 启动所有模拟器 firebase emulators:start --only firestore,auth # 启动指定模拟器

Deploy

部署命令

firebase deploy # Deploy everything firebase deploy --only firestore # Deploy rules + indexes firebase deploy --only functions # Deploy functions firebase deploy --only hosting # Deploy hosting
firebase deploy # 部署所有资源 firebase deploy --only firestore # 部署Firestore规则和索引 firebase deploy --only functions # 部署云函数 firebase deploy --only hosting # 部署静态站点

Functions

云函数操作

cd functions && npm run build # Build TypeScript firebase functions:log # View logs

---
cd functions && npm run build # 构建TypeScript代码 firebase functions:log # 查看云函数日志

---

Anti-Patterns

反模式

  • No security rules - Always write rules, never use test mode in production
  • Deep nesting - Keep documents flat, max 2-3 levels
  • Large documents - Max 1MB, split if larger
  • Unbounded arrays - Use subcollections for lists that grow
  • No offline handling - Enable persistence for mobile/PWA
  • Reading all fields - Use field masks or Firestore Lite
  • Ignoring indexes - Check console for missing index errors
  • No emulator testing - Always test rules before deploy
  • 未配置安全规则 - 务必编写安全规则,生产环境绝不能使用测试模式
  • 深度嵌套 - 保持文档扁平化,最多2-3级
  • 超大文档 - 文档大小上限为1MB,超过则拆分
  • 无界数组 - 对于会持续增长的列表,使用子集合
  • 未处理离线场景 - 为移动应用或PWA启用离线持久化
  • 读取所有字段 - 使用字段掩码或Firestore Lite
  • 忽略索引 - 检查控制台是否有缺失索引的错误提示
  • 未使用模拟器测试 - 部署前务必测试规则