Spotted a bug? Have a great idea? Help us make google.dev great!

Serverless backend tools like Cloud Firestore and Cloud Functions are very easy to use, but can be hard to test. The Firebase Local Emulator Suite allows you to run local versions of these services on your development machine so you can develop your app quickly and safely.

What you'll need

What you'll build

In this codelab, you run and debug a simple online shopping app which is powered by multiple Firebase services:

  • Cloud Firestore: A globally scalable, serverless, NoSQL database with real-time capabilities.
  • Firebase Hosting: A fast and secure hosting for web apps.
  • Cloud Functions: A serverless backend code that runs in response to events or HTTP requests.

You will connect the app to the Emulator Suite to enable local development.

What you'll learn

  • How to connect your app to the Emulator Suite.
  • How the various emulators are connected.

Create a Firebase project

If you don't have a Firebase project, in the Firebase console, create a new Firebase project. Make a note of the Project ID you choose, you will need it later.

Configure your project

In the Firebase console, navigate to the Authentication pane and then to the Sign-in method tab. Make sure the Anonymous provider is enabled.

Anonymous authentication allows you to assign each user that visits your page with a random, persistent user ID that uniquely identifies them to the backend.

Get the source code

In this codelab, you start off with a version of The Fire Store sample that is nearly complete, so the first thing you need to do is clone the source code:

$ git clone https://github.com/firebase/emulators-codelab.git

OR

# If you use two-factor authentication on GitHub
$ git clone git@github.com:firebase/emulators-codelab.git

Then move into the codelab directory, where you will work for the remainder of this codelab:

$ cd emulators-codelab/codelab-initial-state

Now, install the dependencies so you can run the code. If you're on a slower internet connection this may take a minute or two:

# This is equivalent to "cd functions && npm install" 
$ npm --prefix=functions install

Get the Firebase CLI

The Emulator Suite is part of the Firebase CLI (command-line interface) which can be installed on your machine with the following command:

$ npm install -g firebase-tools

Next, confirm that you have the CLI version 7.3.0 or higher:

$ firebase --version
7.3.2

Connect to your Project

Now, link this code to your Firebase project. You can use any existing Firebase project for this codelab.

Run the following command to create a project alias. First, select your project from the list. When asked what alias you want to use, choose default.

$ firebase use --add

The output should look like the following example. Remember to choose your actual Firebase project from the list:

? Which project do you want to add? YOUR_PROJECT_ID
? What alias do you want to use for this project? (e.g. staging) default

Created alias default for YOUR_PROJECT_ID.
Now using alias default (YOUR_PROJECT_ID)

Now you're ready to run the app!

In this section, you'll run the app locally. This means it is time to boot up the Emulator Suite.

Start the Emulators

From inside the codelab source directory, run the following command to start the emulators:

$ firebase emulators:start

You should see some output like this:

$ firebase emulators:start
i  Starting emulators: ["functions","firestore","hosting"]
✔  functions: Using node@8 from host.
✔  functions: Emulator started at http://localhost:5001
i  firestore: Serving WebChannel traffic on at http://localhost:8081
i  firestore: Emulator logging to firestore-debug.log
✔  firestore: Emulator started at http://localhost:8080
i  firestore: For testing set FIRESTORE_EMULATOR_HOST=localhost:8080
i  hosting: Serving hosting files from: public
✔  hosting: Local server: http://localhost:5000
✔  hosting: Emulator started at http://localhost:5000
i  functions: Watching "/usr/local/google/home/samstern/Scratch/emulator-codelabs/emulator-codelab/codelab-final-state/functions" for Cloud Functions...
✔  functions[calculateCart]: firestore function initialized.
✔  All emulators started, it is now safe to connect

Once you see the All emulators started message, the app is ready to use.

Connect the web app to the Cloud Firestore Emulator

We can see that the Cloud Firestore emulator is serving web traffic on the port 8081 according to this line from the logs:

i  firestore: Serving WebChannel traffic on at http://localhost:8081

Let's connect your frontend code to the emulator, rather than to production. Open the public/js/homepage.js file and find the onDocumentReady function. You can see that the function accesses the standard Firestore instance:

public/js/homepage.js

  const db = firebaseApp.firestore();

Let's update the db object to point to the Firestore emulator:

public/js/homepage.js

  const db = firebaseApp.firestore();

  // ADD THESE LINES
  if (window.location.hostname === "localhost") {
    console.log("localhost detected!");
    db.settings({
      host: "localhost:8081",
      ssl: false
    });
  }

Now when the app is running on localhost (served by the the Hosting emulator) the Firestore client also points at the local emulator rather than at a production database.

Open the app

In your web browser, navigate to http://localhost:5000 and you should see The Fire Store running locally on your machine!

[Optional] How do I know this is safe?

You might be worried that the codelab is actually writing data to your production database. This is not the case! All reads and writes are handled locally by the Cloud Firestore emulator. We can guarantee that this is the case using Security Rules.

If you created a new project for this codelab, navigate to the Database tab of the Firebase console, choose Firestore, and click Create Database. You'll be asked to choose a mode for your new Cloud Firestore database:

Choose Locked Mode and continue. This means that your web app cannot read or write any data to your production database. Your app only communicates with the emulator. All of the data you're working with is 100% local and is erased when the emulator shuts down.

Use the app

Pick an item on the homepage and click Add to Cart. Unfortunately, you will run into the following error:

Let's fix that bug! Because everything is running in the emulators, we can experiment and not worry about affecting real data.

Find the bug

Ok let's look in the Chrome developer console. Press Control+Shift+J (Windows, Linux, Chrome OS) or Command+Option+J (Mac) to see the error on the console:

It seems like there was some error in the addToCart method, let's take a look at that. Where do we try to access something called uid in that method and why would it be null? Right now the method looks like this in public/js/homepage.js:

public/js/homepage.js

  addToCart(id, itemData) {
    console.log("addToCart", id, JSON.stringify(itemData));
    return this.db
      .collection("carts")
      .doc(this.auth.currentUser.uid)
      .collection("items")
      .doc(id)
      .set(itemData);
  }

Aha! We're not signed into the app. According to the Firebase Authentication docs, when we are not signed in, auth.currentUser is null. Let's add a check for that:

public/js/homepage.js

  addToCart(id, itemData) {
    // ADD THESE LINES
    if (this.auth.currentUser === null) {
      this.showError("You must be signed in!");
      return;
    }

    // ...
  }

Test the app

Now, refresh the page and then click Add to Cart. You should get a nicer error this time:

But if you click Sign In in the upper toolbar and then click Add to Cart again, you will see that the cart is updated.

However, it doesn't look like the numbers are correct at all:

Don't worry, we'll fix that bug soon. First, let's dive deep into what actually happened when you added an item to your cart.

Clicking Add to Cart kicks off a chain of events that involve multiple emulators. In the Firebase CLI logs, you should see something like the following messages after you add an item to your cart:

i  functions: Beginning execution of "calculateCart"
i  functions: Finished "calculateCart" in ~1s

There were four key events that occurred to produce those logs and the UI update you observed:

1) Firestore Write - Client

A new document is added to the Firestore collection /carts/{cartId}/items/{itemId}/. You can see this code in the addToCart function inside public/js/homepage.js:

public/js/homepage.js

  addToCart(id, itemData) {
    // ...
    console.log("addToCart", id, JSON.stringify(itemData));
    return this.db
      .collection("carts")
      .doc(this.auth.currentUser.uid)
      .collection("items")
      .doc(id)
      .set(itemData);
  }

2) Cloud Function Triggered

The Cloud Function calculateCart listens for any write events (create, update, or delete) that happen to cart items by using the onWrite trigger, which you can see in functions/index.js:

functions/index.js

exports.calculateCart = functions.firestore
    .document("carts/{cartId}/items/{itemId}")
    .onWrite(async (change, context) => {
      try {
        let totalPrice = 125.98;
        let itemCount = 8;

        const cartRef = db.collection("carts").doc(context.params.cartId);

        return await cartRef.update({
          totalPrice,
          itemCount
        });
      } catch(err) {
      }
    }
);

3) Firestore Write - Admin

The calculateCart function reads all of the items in the cart and adds up the total quantity and price, then it updates the "cart" document with the new totals (see cartRef.update(...) above).

4) Firestore Read - Client

The web frontend is subscribed to receive updates about changes to the cart. It gets a real-time update after the Cloud Function writes the new totals and updates the UI, as you can see in public/js/homepage.js:

public/js/homepage.js

this.cartUnsub = cartRef.onSnapshot(cart => {
   // The cart document was changed, update the UI
   // ...
});

Nice work! You just set up a fully local app that uses three different Firebase emulators for fully local testing.

But wait, there's more! In the next codelab you'll learn:

  • How to write unit tests that use the Firebase Emulators.
  • How to use the Firebase Emulators to debug your Security Rules.