iTranslated by AI
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 ↓
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.
- "Which Firebase CLI features do you want to set up for this folder?"
- Select Firestore and Emulators
- And any others you need
- "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?)
- "What file should be used for Firestore Rules?"
- "What file should be used for Firestore indexes?"
- "Which Firebase emulators do you want to set up?"
- Select Firestore
- And others like Authentication if needed
- "Which port do you want to use for the firestore emulator?"
- "Would you like to enable the Emulator UI?"
- "Which port do you want to use for the Emulator UI"
- "Would you like to download the emulators now?"
-
yto start downloading - If you accidentally chose
N, don't worry—just continue and re-runinitnext time. You can also download just the emulator withnpx firebase setup:emulators:firestore.
-
- "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.)
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.
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) ↓
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.
Discussion