home / skills / trantuananh-17 / product-reviews / firestore

firestore skill

/.claude/skills/firestore

This skill helps you optimize Firestore queries, indexing, and batch operations with best practices to improve performance and reliability.

npx playbooks add skill trantuananh-17/product-reviews --skill firestore

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
3.2 KB
---
name: firestore-database
description: Use this skill when the user asks about "Firestore", "database queries", "indexes", "batch operations", "pagination", "TTL", "write limits", or any Firestore-related work. Provides Firestore query optimization, indexing, and best practices.
---

# Firestore Best Practices

## Query Optimization

### Filters & Limits

```javascript
// ❌ BAD: Fetch all, filter in JS
const all = await customersRef.get();
const active = all.docs.filter(d => d.data().status === 'active');

// ✅ GOOD: Filter in query
const active = await customersRef
  .where('status', '==', 'active')
  .where('shopId', '==', shopId)
  .limit(100)
  .get();
```

### Batch Reads

```javascript
// ❌ BAD: Read in loop (N reads)
for (const id of customerIds) {
  const doc = await customerRef.doc(id).get();
}

// ✅ GOOD: Batch read (1 operation)
const docs = await firestore.getAll(
  ...customerIds.map(id => customerRef.doc(id))
);
```

### Check Empty Collections

```javascript
// ❌ BAD: Uses .size
if (snapshot.size === 0) { }

// ✅ GOOD: Uses .empty (fast)
if (snapshot.empty) { }
```

---

## Batch Operations

```javascript
const batch = firestore.batch();
const BATCH_SIZE = 500;

for (let i = 0; i < items.length; i += BATCH_SIZE) {
  const chunk = items.slice(i, i + BATCH_SIZE);
  chunk.forEach(item => {
    batch.set(collectionRef.doc(item.id), item);
  });
  await batch.commit();
}
```

---

## Indexes

### Index File Structure

If `firestore-indexes/` folder exists, **always add indexes there** (not directly to `firestore.indexes.json`):

```
firestore-indexes/
├── build.js              # Merge all → firestore.indexes.json
├── split.js              # Split into collection files
├── customers.json        # Indexes for customers
└── {collection}.json     # One file per collection
```

### Workflow

1. Create/edit `firestore-indexes/{collection}.json`
2. Run `yarn firestore:build` to regenerate `firestore.indexes.json`

| Command | Description |
|---------|-------------|
| `yarn firestore:build` | Merge into firestore.indexes.json |
| `yarn firestore:split` | Split into collection files |

### When Index Required

| Query Pattern | Index Needed? |
|---------------|---------------|
| Single field `where()` | NO (auto) |
| `where()` + `orderBy()` different fields | YES |
| Multiple inequality `where()` | YES |

---

## Index Exemptions

Use for large fields you don't query:

```json
{
  "fieldOverrides": [
    {
      "collectionGroup": "webhookLogs",
      "fieldPath": "body",
      "indexes": []
    }
  ]
}
```

---

## Write Rate Limits

**Limit: 1 write per document per second**

```javascript
// ❌ BAD: Multiple writes to same doc
await shopRef.doc(shopId).update({ lastSyncAt: new Date() });

// ✅ GOOD: Write to separate collection
await shopUpdatesRef.add({
  shopId,
  lastSyncAt: new Date(),
  expiredAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
});
```

---

## Repository Pattern

**ONE repository = ONE collection**

```javascript
const customersRef = firestore.collection('customers');

export const getByShop = (shopId) =>
  customersRef.where('shopId', '==', shopId).get();

export const update = (id, data) =>
  customersRef.doc(id).update({ ...data, updatedAt: new Date() });
```

Overview

This skill provides concise, practical guidance for designing and operating Firestore databases. It focuses on query optimization, indexing strategy, batch operations, pagination, TTL, and write-rate limits to keep Firestore performant and cost-efficient. Use it to get actionable patterns and repository-level recommendations tailored to common Firestore pitfalls.

How this skill works

The skill inspects common Firestore usage patterns and suggests concrete fixes: convert client-side filtering into indexed queries, consolidate reads and writes into batches, and structure indexes per collection. It highlights when composite indexes are required, how to exempt large unqueried fields, and how to avoid per-document write contention by using update logs or separate collections. It also provides repository-level conventions for one collection per repository.

When to use it

  • You need to speed up slow queries or reduce billing from excessive document reads.
  • You are planning or modifying composite indexes and want a safe workflow for index files.
  • You must perform bulk reads or writes without hitting quota or rate limits.
  • You need to design TTL, pagination, or avoid hot documents due to frequent updates.
  • You want a maintainable repository pattern for Firestore collections.

Best practices

  • Filter and limit in queries instead of fetching all documents and filtering client-side.
  • Use firestore.getAll or batched reads instead of looping single-document reads.
  • Commit writes in batches of up to 500 operations and chunk large jobs.
  • Avoid frequent writes to the same document; write to a separate updates collection to serialize state.
  • Keep indexes in per-collection files and use a build step to merge them into the deployable config.
  • Use fieldOverrides to exempt large fields (e.g., request bodies) from indexing.

Example use cases

  • Convert a slow admin dashboard that fetches all users into paginated, filtered queries with proper indexes.
  • Bulk-import or export thousands of documents using batched commits of 500 items to stay within limits.
  • Resolve a Firestore error that demands a composite index by adding a collection JSON file and running the build script.
  • Prevent write-throttling on a shop document by appending update entries to a shopUpdates collection with TTL.
  • Implement efficient batch reads for a list of customer IDs using firestore.getAll to reduce RPCs.

FAQ

When do I need a composite index?

A composite index is required when you combine where() and orderBy() on different fields or use multiple inequality where() clauses; single-field where() queries use automatic indexes.

How do I avoid hitting Firestore write limits on a single document?

Limit writes to one per document per second by moving frequent updates into a separate collection (an append-only log) and materialize state asynchronously if needed.