Get the FREE Ultimate OpenClaw Setup Guide →
npx machina-cli add skill JanSzewczyk/claude-plugins/firebase-firestore --openclaw
Files (1)
SKILL.md
7.2 KB

Firebase Firestore Skill

Create production-ready Firestore database queries with TypeScript, proper type lifecycle, structured error handling, and best practices.

Quick Reference

DocumentPurpose
types.mdType utilities and lifecycle patterns
errors.mdDbError class and error categorization
config.mdFirebase Admin SDK configuration
examples.mdComplete CRUD operation examples
patterns.mdBest practices and anti-patterns
seeding.mdDatabase seeding patterns

Instructions

Before Implementation

  1. Read project context at .claude/project-context.md for project-specific patterns
  2. Check existing patterns in lib/firebase/ for configuration
  3. Review feature structure in features/*/server/db/ for query patterns

Workflow

  1. Define types using the type lifecycle pattern (Base → Firestore → Application → DTOs)
  2. Create transform function to convert Firestore data to application types
  3. Implement queries with tuple return pattern [DbError | null, Data | null]
  4. Add error handling using DbError class and categorizeDbError
  5. Include logging with structured context at all error points
  6. Test edge cases (empty inputs, not found, permissions)

File Organization

features/
└── [feature]/
    ├── types/
    │   └── [resource].ts          # Type definitions
    └── server/
        └── db/
            ├── [resource]-queries.ts  # CRUD operations
            └── seed-[resource].ts     # Seeding (if needed)

Core Concepts

Type Lifecycle

Firebase requires different type representations at different stages:

// 1. Base type - Business fields only
type ResourceBase = {
  name: string;
  status: "active" | "inactive";
};

// 2. Firestore type - With Timestamp objects
type ResourceFirestore = WithFirestoreTimestamps<ResourceBase>;

// 3. Application type - With id and Date objects
type Resource = WithDates<ResourceBase>;

// 4. Create DTO - For creating documents
type CreateResourceDto = CreateDto<ResourceBase>;

// 5. Update DTO - For updating documents
type UpdateResourceDto = UpdateDto<ResourceBase>;

See types.md for complete type utilities.

Error Handling Pattern

All database queries return tuples for explicit error handling:

export async function getResourceById(
  id: string,
): Promise<[null, Resource] | [DbError, null]> {
  // Input validation
  if (!id?.trim()) {
    return [DbError.validation("Invalid id provided"), null];
  }

  try {
    const doc = await db.collection(COLLECTION).doc(id).get();

    if (!doc.exists) {
      return [DbError.notFound(RESOURCE_NAME), null];
    }

    return [null, transformToResource(doc.id, doc.data()!)];
  } catch (error) {
    return [categorizeDbError(error, RESOURCE_NAME), null];
  }
}

See errors.md for complete error handling.

Transform Functions

Convert Firestore documents to application types:

function transformToResource(
  docId: string,
  data: FirebaseFirestore.DocumentData,
): Resource {
  return {
    id: docId,
    ...data,
    // Convert Timestamp to Date
    createdAt: data.createdAt?.toDate(),
    updatedAt: data.updatedAt?.toDate(),
  } as Resource;
}

Questions to Ask

Before implementing database queries, clarify:

  1. What is the resource name? (e.g., "Budget", "User", "Category")
  2. What fields does the resource have? (business fields only)
  3. Are there custom Date fields? (beyond createdAt/updatedAt)
  4. What queries are needed? (getById, getAll, getByUserId, etc.)
  5. Is seeding required? (predefined data)
  6. What are the access patterns? (by user, by status, etc.)

Usage Examples

Basic Query Function

import "server-only";
import { db } from "~/lib/firebase";
import { categorizeDbError, DbError } from "~/lib/firebase/errors";
import { createLogger } from "~/lib/logger";
import type { Budget } from "../types/budget";

const logger = createLogger({ module: "budget-db" });
const COLLECTION = "budgets";
const RESOURCE = "Budget";

export async function getBudgetById(
  id: string,
): Promise<[null, Budget] | [DbError, null]> {
  if (!id?.trim()) {
    const error = DbError.validation("Invalid budget id");
    logger.warn({ errorCode: error.code }, "Validation failed");
    return [error, null];
  }

  try {
    const doc = await db.collection(COLLECTION).doc(id).get();

    if (!doc.exists) {
      logger.warn({ budgetId: id }, "Budget not found");
      return [DbError.notFound(RESOURCE), null];
    }

    logger.info({ budgetId: id }, "Budget retrieved");
    return [null, transformToBudget(doc.id, doc.data()!)];
  } catch (error) {
    const dbError = categorizeDbError(error, RESOURCE);
    logger.error({ budgetId: id, errorCode: dbError.code }, "Query failed");
    return [dbError, null];
  }
}

Usage in Server Actions

export async function updateBudget(
  budgetId: string,
  data: UpdateBudgetDto,
): ActionResponse<Budget> {
  const { userId } = await auth();
  if (!userId) {
    return { success: false, error: "Unauthorized" };
  }

  const [error, budget] = await updateBudgetInDb(budgetId, data);

  if (error) {
    if (error.isNotFound) {
      return { success: false, error: "Budget not found" };
    }
    return { success: false, error: error.message };
  }

  revalidatePath("/budgets");
  return { success: true, data: budget };
}

Usage in Page Loaders

async function loadBudget(budgetId: string) {
  const { userId } = await auth();
  if (!userId) redirect("/sign-in");

  const [error, budget] = await getBudgetById(budgetId);

  if (error) {
    if (error.isNotFound) notFound();
    if (error.isRetryable) throw error; // Let error.tsx handle
    throw new Error("Unable to load budget");
  }

  return budget;
}

Related Documentation

Related Skills

  • server-actions - For implementing server actions that use these queries
  • db-migration - For migrating Firestore data

Source

git clone https://github.com/JanSzewczyk/claude-plugins/blob/main/plugins/firebase-auth/skills/firebase-firestore/SKILL.mdView on GitHub

Overview

Build production-ready Firestore queries with TypeScript, enforcing a type lifecycle from base types to DTOs. This approach delivers robust error handling, structured logging, and server-safe patterns for Next.js apps.

How This Skill Works

Define a Type Lifecycle: Base → Firestore → Application → DTOs. Create a transform function to map Firestore data to your app types, and implement queries that return a [DbError|null, Data|null] tuple. Use DbError and categorizeDbError for error handling, and log context at every error point.

When to Use It

  • Building read/write queries for user profiles in Next.js API routes
  • Implementing CRUD operations for budget management
  • Adding robust error handling to Firestore queries
  • Seeding Firestore with predefined data
  • Enforcing server-only data access patterns with proper types

Quick Start

  1. Step 1: Define the Type Lifecycle (Base → Firestore → Application → DTOs) for your resource
  2. Step 2: Implement transformToResource and CRUD queries that return [DbError|null, Data|null]
  3. Step 3: Use categorizeDbError and structured logging; test edge cases (empty input, not found, permissions)

Best Practices

  • Apply the Type Lifecycle by transitioning from Base to Firestore to Application types and then to DTOs
  • Return [DbError|null, Data|null] tuples for all queries and handle with categorizeDbError
  • Validate inputs early and handle not-found and permission errors explicitly
  • Use a dedicated transformToResource function to map Firestore docs to application types
  • Log structured context at every error point to aid debugging and monitoring

Example Use Cases

  • Create database queries for user profiles
  • Implement CRUD operations for budget management
  • Add error handling to Firestore queries
  • Set up Firebase seeding for predefined data
  • Transform Firestore documents into application types using transformToResource

Frequently Asked Questions

Add this skill to your agents

Related Skills

Sponsor this space

Reach thousands of developers