Firebase Firestore REST API client for Edge runtime environments like Cloudflare Workers and Vercel Edge Functions.
- Works in Edge runtime environments where Firebase Admin SDK is not available
- Full CRUD operations support
- TypeScript support
- Token caching for better performance
- Simple and intuitive API
- Explicit configuration without hidden environment variable dependencies
npm install firebase-rest-firestore
import { createFirestoreClient } from "firebase-rest-firestore";
// Create a client with your configuration
const firestore = createFirestoreClient({
projectId: "your-project-id",
privateKey: "your-private-key",
clientEmail: "your-client-email",
});
// Add a document
const newDoc = await firestore.add("collection", {
name: "Test Document",
value: 100,
});
// Get a document
const doc = await firestore.get("collection", newDoc.id);
// Update a document
await firestore.update("collection", newDoc.id, { value: 200 });
// Query documents
const querySnapshot = await firestore
.collection("games")
.where("score", ">", 50)
.where("active", "==", true)
.orderBy("score", "desc")
.limit(10)
.get();
// Process query results
const games = [];
querySnapshot.forEach(doc => {
games.push({
id: doc.id,
...doc.data(),
});
});
console.log("Games with score > 50:", games);
// Delete a document
await firestore.delete("collection", newDoc.id);
The FirestoreConfig
object requires the following properties:
Property | Description |
---|---|
projectId | Firebase project ID |
privateKey | Service account private key |
clientEmail | Service account client email |
The main class for interacting with Firestore.
Creates a new document with an auto-generated ID in the specified collection.
Parameters:
data
: Document data to be added
Returns: A reference to the created document.
Creates or overwrites a document with the specified ID. If no ID is provided, one will be auto-generated.
Parameters:
id
(optional): Document IDdata
: Document data
Returns: A promise that resolves when the set operation is complete.
Retrieves a document by ID.
Updates an existing document.
Deletes a document.
Queries documents in a collection with filtering, ordering, and pagination.
Creates a new FirestoreClient instance with the provided configuration.
Adds a new document to the specified collection.
Parameters:
collectionName
: Name of the collectiondata
: Document data to be added
Returns: The added document with auto-generated ID.
Firebase REST Firestore throws exceptions with appropriate error messages when API requests fail. Here's an example of error handling:
try {
// Try to get a document
const game = await firestore.get("games", "non-existent-id");
// If document doesn't exist, null is returned
if (game === null) {
console.log("Document not found");
return;
}
// Process document if it exists
console.log("Fetched game:", game);
} catch (error) {
// Handle API errors (authentication, network, etc.)
console.error("Firestore error:", error.message);
}
Common error cases:
- Authentication errors (invalid credentials)
- Network errors
- Invalid query parameters
- Firestore rate limits
The query
method supports the following options for filtering, sorting, and paginating Firestore documents:
Specify multiple filter conditions. Each condition is an object with the following properties:
field
: The field name to filter onop
: The comparison operator. Available values:EQUAL
: Equal toNOT_EQUAL
: Not equal toLESS_THAN
: Less thanLESS_THAN_OR_EQUAL
: Less than or equal toGREATER_THAN
: Greater thanGREATER_THAN_OR_EQUAL
: Greater than or equal toARRAY_CONTAINS
: Array containsIN
: Equal to any of the specified valuesARRAY_CONTAINS_ANY
: Array contains any of the specified valuesNOT_IN
: Not equal to any of the specified values
value
: The value to compare against
// Query games with score > 50 and active = true
const games = await firestore.query("games", {
where: [
{ field: "score", op: "GREATER_THAN", value: 50 },
{ field: "active", op: "EQUAL", value: true },
],
});
Specifies the field name to sort results by. Results are sorted in ascending order by default.
// Sort by creation time
const games = await firestore.query("games", {
orderBy: "createdAt",
});
Limits the maximum number of results returned.
// Get at most 10 documents
const games = await firestore.query("games", {
limit: 10,
});
Specifies the number of results to skip. Useful for pagination.
// Skip the first 20 results and get the next 10
const games = await firestore.query("games", {
offset: 20,
limit: 10,
});
Example of a compound query:
// Get top 10 active games by score
const topGames = await firestore.query("games", {
where: [{ field: "active", op: "EQUAL", value: true }],
orderBy: "score", // Sort by score
limit: 10,
});
// Set these environment variables in wrangler.toml
// FIREBASE_PROJECT_ID
// FIREBASE_PRIVATE_KEY
// FIREBASE_CLIENT_EMAIL
import { createFirestoreClient } from "firebase-rest-firestore";
export default {
async fetch(request, env, ctx) {
// Load configuration from environment variables
const firestore = createFirestoreClient({
projectId: env.FIREBASE_PROJECT_ID,
privateKey: env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, "\n"),
clientEmail: env.FIREBASE_CLIENT_EMAIL,
});
const url = new URL(request.url);
const path = url.pathname;
// Example API endpoint
if (path === "/api/games" && request.method === "GET") {
try {
// Get active games
const games = await firestore.query("games", {
where: [{ field: "active", op: "EQUAL", value: true }],
limit: 10,
});
return new Response(JSON.stringify(games), {
headers: { "Content-Type": "application/json" },
});
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
}
return new Response("Not found", { status: 404 });
},
};
// Set these environment variables in .env.local
// FIREBASE_PROJECT_ID
// FIREBASE_PRIVATE_KEY
// FIREBASE_CLIENT_EMAIL
import { createFirestoreClient } from "firebase-rest-firestore";
export const config = {
runtime: "edge",
};
export default async function handler(request) {
// Load configuration from environment variables
const firestore = createFirestoreClient({
projectId: process.env.FIREBASE_PROJECT_ID,
privateKey: process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, "\n"),
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
});
try {
// Get the latest 10 documents
const documents = await firestore.query("posts", {
orderBy: "createdAt",
limit: 10,
});
return new Response(JSON.stringify(documents), {
headers: { "Content-Type": "application/json" },
});
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
}
Firebase REST Firestore caches JWT tokens to improve performance. By default, tokens are cached for 50 minutes (actual token expiry is 1 hour). This eliminates the need to generate a new token for each request, improving API request speed.
// Tokens are cached internally, so multiple requests
// have minimal authentication overhead
const doc1 = await firestore.get("collection", "doc1");
const doc2 = await firestore.get("collection", "doc2");
const doc3 = await firestore.get("collection", "doc3");
When dealing with large amounts of data, consider the following:
-
Set appropriate limits: Always use the
limit
parameter to restrict the number of documents returned. -
Query only needed fields: Future versions will add support for retrieving only specific fields.
-
Create indexes: For complex queries, create appropriate indexes in the Firebase console.
-
Use pagination: When retrieving large datasets, implement pagination using
offset
andlimit
.
In edge environments, be aware of:
-
Cold starts: Initial execution has token generation overhead.
-
Memory usage: Be mindful of memory limits when processing large amounts of data.
-
Timeouts: Long-running queries may hit edge environment timeout limits.
- Batch operations: The current version does not support batch processing for operating on multiple documents at once.
- Transactions: Atomic transaction operations are not supported.
- Real-time listeners: Due to the nature of REST APIs, real-time data synchronization is not supported.
- Subcollections: The current version has limited direct support for nested subcollections.
The following features are planned for future versions:
- Batch operations support
- Basic transaction support
- Improved subcollection support
- More detailed query options (compound indexes, etc.)
- Performance optimizations
Please report feature requests and bugs via GitHub Issues.
MIT