iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🍣

Local Development with Firebase Firestore Emulator

に公開

Maybe you all already knew this. I just found out recently. (It is mentioned normally in the documentation.)

Summary first

  • If you have an emulator, you can develop mostly locally
  • You can browse and edit via a web UI like the Firebase Console
  • Install with npx firebase init
  • Start with npx firebase emulators:start
  • Connect from the app with app.firestore().useEmulator("localhost", 8080);
  • Official: Overview of Firebase Local Emulator Suite
  • Example: ginpei/firebase-emu-example

There are other Firebase features besides Firestore, but in this article, I will mainly talk about Firestore.

Note: It looks like this ↓

Screenshot of the web UI. It resembles the Firestore screen in the Firebase console

What you can do with the emulator

  • No need to worry about real Firestore data
  • No more sharing the same database among multiple people
  • Test security rules before deploying them
  • Resume from snapshots for behavior verification
  • Write tests for security rules, etc.
  • Tests can be run entirely locally and also work in CI

What you cannot do with the emulator

  • It is not an emulator for the entire Firebase platform, so it won't be completely local
    • For example, there is no Storage emulator
    • It might increase over time? (Auth was not available in the first edition of this article)
  • You cannot use it as a replacement for the actual Firebase to release an app.

Installation

You can install it via firebase init, just like other Firebase features.

By the way, you don't need to worry about re-running init even if you have already configured Firebase. (This applies to more than just the emulator.) Also, if you only want to add the emulator, specifying firebase init emulator from the start seems to shorten the process slightly.

Prerequisites

If you haven't installed firebase-tools yet, please prepare it. Since we're talking about Firebase, you probably already have it.

$ npm install -D firebase-tools

Here, I'm installing it as a development dependency (-D), but you can install it globally (-g) if you prefer.

If the version is displayed without error, you're good to go.

$ npx firebase --version

Installing the Emulator

$ npx firebase init

The interactive installer will start. For the most part, it's fine to proceed with the default values.

  1. "Which Firebase CLI features do you want to set up for this folder?"
    • Select Firestore and Emulators
    • And any others you need
  2. "Please select an option"
    • Select an existing project or create a new one
    • Note: If you choose "Don't set up a default project" here, the web UI won't appear (why?)
  3. "What file should be used for Firestore Rules?"
  4. "What file should be used for Firestore indexes?"
  5. "Which Firebase emulators do you want to set up?"
    • Select Firestore
    • And others like Authentication if needed
  6. "Which port do you want to use for the firestore emulator?"
  7. "Would you like to enable the Emulator UI?"
  8. "Which port do you want to use for the Emulator UI"
  9. "Would you like to download the emulators now?"
    • y to start downloading
    • If you accidentally chose N, don't worry—just continue and re-run init next time. You can also download just the emulator with npx firebase setup:emulators:firestore.
  10. "Firebase initialization complete!"

Great job.

Startup

It will occupy the terminal while running, so please prepare a separate tab or something. (If you are on Windows, use the official Windows Terminal instead of cmd.exe.)

$ npm firebase emulators:start



┌───────────┬────────────────┬─────────────────────────────────┐
 Emulator Host:Port View in Emulator UI
├───────────┼────────────────┼─────────────────────────────────┤
 Firestore localhost:8080 http://localhost:4000/firestore
└───────────┴────────────────┴─────────────────────────────────┘
  Other reserved ports: 4400, 4500

Issues? Report them at https://github.com/firebase/firebase-tools/issues and attach the *-debug.log files.

As stated, localhost:4000 is the web UI where you can enter Firestore data and more. localhost:8080 is the one the app connects to.

Stopping

Stop with Ctrl+C.

Executing Commands

Using exec runs a specified command along with the emulator, and the emulator will shut down as soon as it finishes.

$ npx firebase emulators:exec "npm start"

In this case, the web UI doesn't seem to start.

Also, it sometimes crashes in a strange way and remains silent while still holding onto the port, so I don't trust it very much.

Connecting the app to the emulator

To connect Firestore to the emulator using JavaScript, do this:

firebase.firestore().useEmulator("localhost", 8080);

Until recently, it was configured with firestore().settings(), but now it seems better to use useEmulator().

Specific example

function initializeFirebase() {
  firebase.initializeApp({
    apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    appId: "x-local-emu",
    authDomain: "x-local-emu.firebaseapp.com",
    projectId: "x-local-emu",
  });

  const isEmulating = window.location.hostname === "localhost";
  if (isEmulating) {
    firebase.auth().useEmulator("http://localhost:9099");
    firebase.functions().useEmulator("localhost", 5001);
    firebase.firestore().useEmulator("localhost", 8080);
  }
}

Web UI

If you open http://localhost:4000 in your browser, various options will appear, allowing you to browse and edit content. (The port number may change. Please check the terminal output.)

Screenshot of the Web UI top page. Firestore and other emulators are listed

Now you can try various things locally without worrying about the data on Google's servers.

Check that updates are made in sync with the app's behavior and that inputs in the Web UI are reflected in the app. Awesome.

Screenshot of the web UI. It resembles the Firestore screen in the Firebase console

Saving and Loading Data (Import, Export)

Basically, data is discarded when the emulator terminates, but you can also save it and reload the same content.

I think this is useful for repeatedly testing app behavior manually or for E2E testing.

Saving (Export)

While the emulator is running, run this from another terminal ↓.

$ npx firebase emulators:export path/to/export

Files will be output under the specified directory. I thought it would be JSON, but it's binary. That makes sense.

Loading (Import)

When starting the emulator, add the --import option and specify the exported directory.

$ npx firebase emulators:start --import=path/to/export

If the directory doesn't exist, it will crash silently without any detailed error information.

Auto-save

By providing the --export-on-exit option, it can also automatically save when the emulator terminates. This is convenient for trial and error while running the app.

You can specify a destination directory, but if you omit it and use it together with --import, it automatically writes to the same location. This is likely more user-friendly.

$ npx firebase emulators:start --import=path/to/export --export-on-exit

Testing Code Connecting to Firestore and Security Rules

From JavaScript, you can test effectively using the npm package @firebase/rules-unit-testing. I will write an article about this soon.

Security Rules

More details on this in another article as well.

When working with Firestore, you need to wrestle with editing firestore.rules and configuring Security rules.

By using the emulator, you can check the behavior of rules locally without deploying. On the flip side, what's in the production Firestore is not included in the emulator.

Note: The one in the production Firestore (Console > Firestore > Rules > Rules Playground) ↓

Screenshot of a settings screen that looks useful for verifying rules

Emulators other than Firestore

Here is a list of them. (As of firebase v8.2.1)

  • Authentication
  • Cloud Functions
  • Firestore
  • Realtime Database
  • Hosting
  • Cloud Pub/Sub

Hosting can also be used as a development server, although it doesn't have features like automatic reloading.

When configured to use all of them, `firebase.json` looks like this.
{
  "database": {
    "rules": "database.rules.json"
  },
  "firestore": {
    "rules": "firestore.rules",
    "indexes": "firestore.indexes.json"
  },
  "hosting": {
    "public": "public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ]
  },
  "storage": {
    "rules": "storage.rules"
  },
  "emulators": {
    "auth": {
      "port": 9099
    },
    "functions": {
      "port": 5001
    },
    "firestore": {
      "port": 8080
    },
    "database": {
      "port": 9000
    },
    "hosting": {
      "port": 5000
    },
    "pubsub": {
      "port": 8085
    },
    "ui": {
      "enabled": true
    }
  },
  "remoteconfig": {
    "template": "remoteconfig.template.json"
  }
}

The code to connect to the emulators is as follows. Only Authentication has a different interface.

firebase.auth().useEmulator("http://localhost:9099");
firebase.functions().useEmulator("localhost", 5001);
firebase.firestore().useEmulator("localhost", 8080);
firebase.database().useEmulator("localhost", 9000);

Functions' URL was like this:

http://localhost:5001/PROJECT_ID/us-central1/FUNCTION_NAME

Authentication Emulator

It seems only email and password authentication can be configured. The real service allows logging in with Google accounts and more.

Names and image URLs can also be configured.

Others

Log files

It outputs several log files, so please add them to your .gitignore to exclude them from the repository.

# Firebase
database-debug.log
firebase-debug.log
firestore-debug.log
pubsub-debug.log
ui-debug.log

It might be okay to just group them as *-debug.log.

Port Already in Use Error

Sometimes you see something like this:

Error: Could not start Firestore Emulator, port taken.

As it says, the port specified in firebase.json (usually 8080) is taken by another app, so you should either close that app or specify a different port.

However, occasionally it seems like... the emulator fails to exit properly and continues to hold the port... maybe...

Sometimes you might see a message like "Skipping because it wasn't found," which is a bit of a mystery.

Emulator Crashes and Doesn't Start

Poof...

$ npx firebase emulators:start --import=path/to/export
i  emulators: Starting emulators: firestore
i  emulators: Shutting down emulators.

Error: An unexpected error has occurred.

This happened when the import destination path was incorrect or not prepared. I'd appreciate more detailed error messages.

Either prepare the import destination properly or start it without the --import option.

Web UI Does Not Appear

Currently fixed. The following is old content.

I'm not sure why, but if a default project isn't set, the Firestore emulator starts but the UI does not.

┌───────────┬────────────────┐
│ Emulator  │ Host:Port      │
├───────────┼────────────────┤
│ Firestore │ localhost:8080 │
└───────────┴────────────────┘

If you set it up in .firebaserc, it will show up. Anything other than an empty string seems to work.

{
  "projects": {
    "default": "xxxxxxxx"
  }
}
┌───────────┬────────────────┬─────────────────────────────────┐
│ Emulator  │ Host:Port      │ View in Emulator UI             │
├───────────┼────────────────┼─────────────────────────────────┤
│ Firestore │ localhost:8080 │ http://localhost:4000/firestore │
└───────────┴────────────────┴─────────────────────────────────┘

I wonder why they don't show it. It's true that the web UI contains links to the actual console, though.

Java Required

Java is required to run the emulator. It will fail to start if Java is not installed.

⚠  firestore: Fatal error occurred:
   Firestore Emulator has exited because java is not installed, you can install it from https://openjdk.java.net/install/,
   stopping all running emulators

For Ubuntu (WSL):

$ sudo apt install default-jdk-headless -y

Limitations

  • The maximum simultaneous connection count is 100.
  • Capacity and other limits follow the Spark plan.

Or so it says. There are several others, so please refer to the documentation.

--help

I'll provide an excerpt. There's not much information...

$ npx firebase --help

Commands:

  emulators:exec [options] <script>                         start the local Firebase emulators, run a test script, then shut down the emulators
  emulators:export [options] <path>                         export data from running emulators
  emulators:start [options]                                 start the local Firebase emulators

  setup:emulators:database                                  downloads the database emulator
  setup:emulators:firestore                                 downloads the firestore emulator
  setup:emulators:pubsub                                    downloads the pubsub emulator
$ npx firebase emulators
Error: emulators is not a Firebase command

Did you mean emulators:exec?

Testing on Android/iOS devices

It's not an emulator, but there is a feature that allows you to run mobile apps on real devices owned by Google.

According to the Pricing, even the free Spark plan allows for 10 virtual devices and 5 physical devices per day.

I haven't used it myself.

Conclusion

Having an emulator makes development much smoother because you can run and test even with incomplete data structures. Furthermore, being able to write tests (especially for security rules) makes it even more productive. Excellent.

References

Discussion