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
jose for JWT authpnpm 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.
FirestoreMain 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 CollectionReferencedoc(path) - Get a DocumentReferencerunTransaction(updateFn, options?) - Run a transactionDocumentReference<T>Reference to a document.
Properties:
id - Document IDpath - Full document pathparent - Parent CollectionReferenceMethods:
get() - Get document snapshotset(data, options?) - Set document data (options: { merge: boolean })update(data) - Update document fieldsdelete() - Delete documentcollection(path) - Get a subcollectionCollectionReference<T>Reference to a collection.
Properties:
id - Collection IDpath - Full collection pathMethods:
doc(id?) - Get a DocumentReference (auto-generates ID if not provided)add(data) - Add document with auto-generated IDwhere(field, op, value) - Filter by field valueorderBy(field, direction?) - Order resultslimit(n) / limitToLast(n) - Limit resultsoffset(n) - Skip resultsstartAt(...values) / startAfter(...values) - Cursor paginationendAt(...values) / endBefore(...values) - Cursor paginationselect(...fields) - Field projectionget() - Execute query and return QuerySnapshotcount() - Count matching documentsBuild 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<, <=, >, >= - Comparisonarray-contains, array-contains-any - Array queriesin, not-in - Inclusion queriesFieldValueSpecial 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
GeoPointRepresents a geographic coordinate.
const location = new GeoPoint(37.7749, -122.4194);
TimestampRepresents 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