Fires2REST - v0.3.0
    Preparing search index...

    Fires2REST - v0.3.0

    Fires2REST

    Fires2REST (Firestore REST with Transactions) is a TypeScript library that provides a simple and efficient way to perform transactions and other operations on Firestore documents using the REST API.

    It's a drop-in replacement for the official Firebase Admin SDK (the Firestore part), see the comparison.

    Firestore REST with Transactions โ†’ Firestore REST โ†’ FirestoREST โ†’ Fires2REST

    • ๐Ÿ”ฅ Full Firestore REST API support - CRUD operations via REST
    • โšก Transaction support - Atomic reads and writes with automatic retry
    • ๐Ÿ” Query support - Filter, order, limit, and paginate collections
    • ๐ŸŒ Serverless ready - Works in Cloudflare Workers, Deno, Bun, and any JS runtime
    • ๐Ÿงช Emulator support - Local development with Firebase Emulator
    • ๐Ÿ“ฆ Lightweight - The only dependency is jose for JWT auth
    • ๐Ÿ”„ FieldValue support - serverTimestamp, increment, delete, arrayUnion, arrayRemove
    pnpm install fires2rest
    
    import { Firestore, FieldValue } from "fires2rest";

    // Initialize with service account credentials
    const db = Firestore.useServiceAccount("your-project-id", {
    clientEmail: "your-service-account@project.iam.gserviceaccount.com",
    privateKey: "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
    });

    // Get a document
    const userRef = db.doc("users/alice");
    const snap = await userRef.get();

    if (snap.exists) {
    console.log(snap.data()); // { name: "Alice", age: 30 }
    }

    // Set a document
    await userRef.set({
    name: "Alice",
    age: 30,
    createdAt: FieldValue.serverTimestamp(),
    });

    // Update a document
    await userRef.update({
    age: 31,
    visits: FieldValue.increment(1),
    });

    // Delete a document
    await userRef.delete();

    Transactions provide atomic read-modify-write operations:

    // Transfer balance between accounts
    const result = await db.runTransaction(async (txn) => {
    const fromRef = db.doc("accounts/alice");
    const toRef = db.doc("accounts/bob");

    const fromSnap = await txn.get(fromRef);
    const toSnap = await txn.get(toRef);

    const fromBalance = fromSnap.data()?.balance ?? 0;
    const toBalance = toSnap.data()?.balance ?? 0;

    if (fromBalance < 100) {
    throw new Error("Insufficient balance");
    }

    txn.update(fromRef, { balance: fromBalance - 100 });
    txn.update(toRef, { balance: toBalance + 100 });

    return { transferred: 100 };
    });

    See API Reference for detailed documentation.

    Main client class.

    Static Factory Methods:

    // Using service account (for server-side usage)
    const db = Firestore.useServiceAccount(projectId, {
    clientEmail: "service-account@project.iam.gserviceaccount.com",
    privateKey: "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
    }, databaseId?);

    // Using Firebase Emulator (for local development)
    const db = Firestore.useEmulator({
    emulatorHost: "127.0.0.1:8080", // optional, defaults to "127.0.0.1:8080"
    projectId: "demo-project", // optional, defaults to "demo-no-project"
    databaseId: "(default)", // optional, defaults to "(default)"
    admin: true, // optional, defaults to true (bypasses security rules)
    });

    Methods:

    • collection(path) - Get a CollectionReference
    • doc(path) - Get a DocumentReference
    • runTransaction(updateFn, options?) - Run a transaction

    Reference to a document.

    Properties:

    • id - Document ID
    • path - Full document path
    • parent - Parent CollectionReference

    Methods:

    • get() - Get document snapshot
    • set(data, options?) - Set document data (options: { merge: boolean })
    • update(data) - Update document fields
    • delete() - Delete document
    • collection(path) - Get a subcollection

    Reference to a collection.

    Properties:

    • id - Collection ID
    • path - Full collection path

    Methods:

    • doc(id?) - Get a DocumentReference (auto-generates ID if not provided)
    • add(data) - Add document with auto-generated ID
    • where(field, op, value) - Filter by field value
    • orderBy(field, direction?) - Order results
    • limit(n) / limitToLast(n) - Limit results
    • offset(n) - Skip results
    • startAt(...values) / startAfter(...values) - Cursor pagination
    • endAt(...values) / endBefore(...values) - Cursor pagination
    • select(...fields) - Field projection
    • get() - Execute query and return QuerySnapshot
    • count() - Count matching documents

    Build queries using chainable methods:

    // Filter, order, and limit
    const snapshot = await db
    .collection("users")
    .where("age", ">=", 18)
    .where("active", "==", true)
    .orderBy("createdAt", "desc")
    .limit(10)
    .get();

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

    // Count documents
    const countSnap = await db
    .collection("users")
    .where("active", "==", true)
    .count();
    console.log("Active users:", countSnap.data().count);

    // Cursor-based pagination
    const page2 = await db
    .collection("users")
    .orderBy("name")
    .startAfter("Alice")
    .limit(10)
    .get();

    Supported filter operators:

    • ==, != - Equality
    • <, <=, >, >= - Comparison
    • array-contains, array-contains-any - Array queries
    • in, not-in - Inclusion queries

    Special field values for atomic operations.

    FieldValue.serverTimestamp(); // Server-generated timestamp
    FieldValue.increment(n); // Increment numeric field by n
    FieldValue.delete(); // Delete this field
    FieldValue.arrayUnion(...elements); // Add unique elements to array
    FieldValue.arrayRemove(...elements); // Remove elements from array

    Represents a geographic coordinate.

    const location = new GeoPoint(37.7749, -122.4194);
    

    Represents a timestamp with nanosecond precision.

    const now = Timestamp.now();
    const fromDate = Timestamp.fromDate(new Date());
    const fromMillis = Timestamp.fromMillis(Date.now());

    Use generics for type-safe document data:

    interface User {
    name: string;
    email: string;
    age: number;
    }

    const userRef = db.doc("users/alice") as DocumentReference<User>;
    const snap = await userRef.get();

    const user = snap.data(); // User | undefined
    console.log(user?.name); // TypeScript knows this is string

    Use the Firebase Emulator for local development and testing:

    import { Firestore } from "fires2rest";

    // Connect to local emulator with all defaults
    const db = Firestore.useEmulator();

    // Or customize the connection
    const db = Firestore.useEmulator({
    emulatorHost: "127.0.0.1:8095",
    projectId: "demo-no-project",
    admin: true, // bypass security rules
    });

    Start the Firebase Emulator:

    pnpm firebase emulators:start
    

    Set up your service account credentials:

    FIREBASE_PROJECT_ID=your-project-id
    FIREBASE_CLIENT_EMAIL=your-service-account@project.iam.gserviceaccount.com
    FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"

    # For emulator usage
    FIRESTORE_EMULATOR_HOST=127.0.0.1:8095

    MIT