Skip to content

nabettu/firebase-rest-firestore

Repository files navigation

Firebase REST Firestore

日本語版はこちら(Japanese Version)

Firebase Firestore REST API client for Edge runtime environments like Cloudflare Workers and Vercel Edge Functions.

Features

  • 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

Installation

npm install firebase-rest-firestore

Quick Start

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);

Configuration

The FirestoreConfig object requires the following properties:

Property Description
projectId Firebase project ID
privateKey Service account private key
clientEmail Service account client email

API Reference

FirestoreClient

The main class for interacting with Firestore.

collection(collectionPath).add(data)

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.

collection(collectionPath).doc(id?).set(data)

Creates or overwrites a document with the specified ID. If no ID is provided, one will be auto-generated.

Parameters:

  • id (optional): Document ID
  • data: Document data

Returns: A promise that resolves when the set operation is complete.

get(collectionName, documentId)

Retrieves a document by ID.

update(collectionName, documentId, data)

Updates an existing document.

delete(collectionName, documentId)

Deletes a document.

query(collectionName, options)

Queries documents in a collection with filtering, ordering, and pagination.

createFirestoreClient(config)

Creates a new FirestoreClient instance with the provided configuration.

add(collectionName, data)

Adds a new document to the specified collection.

Parameters:

  • collectionName: Name of the collection
  • data: Document data to be added

Returns: The added document with auto-generated ID.

Error Handling

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

Query Options Details

The query method supports the following options for filtering, sorting, and paginating Firestore documents:

where

Specify multiple filter conditions. Each condition is an object with the following properties:

  • field: The field name to filter on
  • op: The comparison operator. Available values:
    • EQUAL: Equal to
    • NOT_EQUAL: Not equal to
    • LESS_THAN: Less than
    • LESS_THAN_OR_EQUAL: Less than or equal to
    • GREATER_THAN: Greater than
    • GREATER_THAN_OR_EQUAL: Greater than or equal to
    • ARRAY_CONTAINS: Array contains
    • IN: Equal to any of the specified values
    • ARRAY_CONTAINS_ANY: Array contains any of the specified values
    • NOT_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 },
  ],
});

orderBy

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",
});

limit

Limits the maximum number of results returned.

// Get at most 10 documents
const games = await firestore.query("games", {
  limit: 10,
});

offset

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,
});

Edge Runtime Examples

Cloudflare Workers

// 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 });
  },
};

Vercel Edge Functions

// 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" },
    });
  }
}

Performance Considerations

Token Caching

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");

Query Optimization

When dealing with large amounts of data, consider the following:

  1. Set appropriate limits: Always use the limit parameter to restrict the number of documents returned.

  2. Query only needed fields: Future versions will add support for retrieving only specific fields.

  3. Create indexes: For complex queries, create appropriate indexes in the Firebase console.

  4. Use pagination: When retrieving large datasets, implement pagination using offset and limit.

Edge Environment Considerations

In edge environments, be aware of:

  1. Cold starts: Initial execution has token generation overhead.

  2. Memory usage: Be mindful of memory limits when processing large amounts of data.

  3. Timeouts: Long-running queries may hit edge environment timeout limits.

Limitations and Roadmap

Current Limitations

  • 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.

Future Roadmap

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.

License

MIT

About

Firebase Firestore REST API client for Edge runtime

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors