skip to Main Content

My Firebase Function runs in the emulator and writes to the same collection and document:

import * as functions from "firebase-functions";

export const MakeUppercase = functions.firestore.document('Messages/{docId}').onCreate((snap, context) => {
  try {
    const original = snap.data().original;
    const uppercase = original.toUpperCase();
    return snap.ref.set({ uppercase }, { merge: true });
  } catch (error) {
    console.error(error);
  }
});

That works great. Now I want to write to a different collection in the Firestore emulator:

import { initializeApp } from "firebase/app";
import * as functions from "firebase-functions";
import { getFirestore, connectFirestoreEmulator } from "firebase/firestore";

const firebaseConfig = {
  apiKey: "...",
  authDomain: "triggerable-functions-project.firebaseapp.com",
  projectId: "triggerable-functions-project",
  storageBucket: "triggerable-functions-project.appspot.com",
  messagingSenderId: "...",
  appId: "..."
};

const app = initializeApp(firebaseConfig);
const db = getFirestore();
connectFirestoreEmulator(db, 'localhost', 8080);

export const MakeUppercase = functions.firestore.document('Messages/{docId}').onCreate((snap, context) => {
  try {
    const original = snap.data().original;
    const uppercase = original.toUpperCase();
    return db.firestore().collection('AnotherCollection').doc(context.params.docId).set({ uppercase }, { merge: true });
  } catch (error) {
    console.error(error);
  }
});

That throws this error:

TypeError: db.firestore is not a function

I either have an incorrect Firestore DocumentReference or I didn’t import a necessary module.

Let’s write to Cloud Firestore. Same code, new module, different DocumentReference:

import * as admin from "firebase-admin";
...
return admin.firestore().collection('AnotherCollection').doc(context.params.docId).set({ uppercase }, { merge: true });

Same error:

TypeError: admin.firestore is not a function

Let’s write to the Storage emulator:

import { getStorage, connectStorageEmulator, ref, uploadString } from "firebase/storage";
...
const storage = getStorage();
connectStorageEmulator(storage, "localhost", 9199);
...
return storage.collection('AnotherCollection').doc(context.params.docId).set({ uppercase }, { merge: true });

That throws the same error:

TypeError: storage.collection is not a function

Let’s write to Cloud Storage:

const storageRef = ref(storage, 'Messages');
...
return uploadString(storageRef, uppercase);

The log says that the function executed without errors but when I go to Firebase Console I don’t see the file in Cloud Storage.

Here’s my final code. Why is app declared but never used?

import { initializeApp } from "firebase/app";
import * as functions from "firebase-functions";
import { getFirestore, connectFirestoreEmulator } from "firebase/firestore";
import { getStorage, connectStorageEmulator, ref, uploadString } from "firebase/storage";
import * as admin from "firebase-admin";

const firebaseConfig = {
  apiKey: "...",
  authDomain: "triggerable-functions-project.firebaseapp.com",
  projectId: "triggerable-functions-project",
  storageBucket: "triggerable-functions-project.appspot.com",
  messagingSenderId: "...",
  appId: "..."
};

const app = initializeApp(firebaseConfig);
const db = getFirestore();
connectFirestoreEmulator(db, 'localhost', 8080);
const storage = getStorage();
const storageRef = ref(storage, 'Messages');
connectStorageEmulator(storage, "localhost", 9199);

export const MakeUppercase = functions.firestore.document('Messages/{docId}').onCreate((snap, context) => {
  try {
    const original = snap.data().original;
    const uppercase = original.toUpperCase();
    return uploadString(storageRef, uppercase);
  } catch (error) {
    console.error(error); // emulator always throws an "unhandled error": "Your function timed out after ~60s."
  }
});

2

Answers


  1. Chosen as BEST ANSWER

    Thanks, @Frank van Puffelen! Here's my Firebase version 9 code for writing to a different directory in the emulator:

    import { initializeApp } from "firebase/app";
    import * as functions from "firebase-functions";
    import { getFirestore, connectFirestoreEmulator, setDoc, doc } from "firebase/firestore";
    
    const firebaseConfig = {
      apiKey: "...",
      authDomain: "triggerable-functions-project.firebaseapp.com",
      projectId: "triggerable-functions-project",
      storageBucket: "triggerable-functions-project.appspot.com",
      messagingSenderId: "...",
      appId: "..."
    };
    
    const firebaseApp = initializeApp(firebaseConfig);
    const db = getFirestore(firebaseApp);
    connectFirestoreEmulator(db, 'localhost', 8080);
    
    export const MakeUppercase = functions.firestore.document('Messages/{docId}').onCreate((snap, context) => {
      try {
        const original = snap.data().original;
        const uppercase = original.toUpperCase();
        return setDoc(
          doc(db, 'AnotherCollection', context.params.docId),
          { uppercase }, { merge: true }
        );
      } catch (error) {
        console.error(error);
      }
    });
    

    Commenting out the emulator

    connectFirestoreEmulator(db, 'localhost', 8080);
    

    writes to Cloud Firestore. This threw an error:

    Connection GRPC stream error. Code: 7 Message: 7 PERMISSION_DENIED: Missing or insufficient permissions.
    

    I'm not going to worry about that, I presume that the emulator isn't intended to write to the Cloud.

    As for Storage, my original code executes without errors but nothing writes to Storage in the emulator. Trying Cloud Storage, my Firebase Console refuses to set up Storage.

    The documentation explains why I kept getting "not a function" errors. Instead of thinking of a Firestore location as a place with an address (in the namespace), in version 9 Firestore locations are functions with parameters.

    While the version 8 APIs are based on a dot-chained namespace and service pattern, version 9's modular approach means that your code will be organized principally around functions.

    David East explained in a blog post that the old (version 8) Firebase managed namespaces on the window but version 9 uses ES modules:

    Historically libraries have been loaded and managed via a namespace on the window, such as window.firebase. This technique does not allow for tree shaking and lacks other benefits of the JavaScript module system.


  2. You’re using the new(ish) modular syntax of the v9 and later SDKs when you call getFirestore. With that new API, most calls are no longer namespaced, but are top-level functions. So there are no longer any public methods on the db object you have (as the error message says), but instead you have to pass the db object when calling those top-level functions.

    The equivalent of this code:

    db.firestore().collection('AnotherCollection').doc(context.params.docId).set({ uppercase }, { merge: true });
    

    Would be:

    setDoc(
      doc(db, 'AnotherCollection', context.params.docId),
      { uppercase }, { merge: true })
    )
    

    Which is pretty close to the code sample in the documentation on setting a document with the v9 syntax, so I recommend keeping that handy while converting the rest of the code to the new API. The Upgrade from version 8 to the modular Web SDK guide is also a good place to get started, as are the blog posts Introducing the new Firebase JS SDK and The new Firebase JS SDK is now GA

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search