'How to import test data into Firebase Firestore emulator from production for easy testing?
I am building a firebase function that listens to a trigger and sends a push notification to users. The trigger is based on firestore data that's too complex for me to manually re-recreate every time on firestore emulator.
I tried emulating functions, but hooking up to firestore production, but seems like in this case, trigger functions don't work 😢
What I want to do is export my data from my production Firestore then import it into my emulator firestore, so that at least, the copy I am working with is closely mimicking what I have on prod; to be clear, the data is small, so I am not worried about downloading terabytes of data.
I found a way of importing data into an emulator, but not sure if this would work with production data or how I would dump the data from production.
firebase emulators:start --import=./some-directory
Solution 1:[1]
You can manually export from Production and then import into the Firestore emulator.
Start by doing an export of your production data to a file. In the example, I export just the "users" collection within an onRequest function so I can kick-off by calling the URL. Note, this this function will not handle subcollections.
exports.exportFirestore = functions.https.onRequest(async (req, res) => {
const fs = require("fs");
const collection = "users";
const fileName = "fs-export.json";
const exportedData = {};
exportedData[collection] = {};
await admin
.firestore()
.collection(collection)
.get()
.then((snapshot) => {
return snapshot.forEach((doc) => {
exportedData[collection][doc.id] = doc.data();
});
})
.catch(console.error);
fs.writeFile(fileName, JSON.stringify(exportedData), (err) => {
if (err) {
console.log(err);
} else {
console.log("Firestore Export Complete.");
res.send("Firestore Export Complete.");
}
});
});
Next, import the file into the local Firestore emulator. Very important: be sure to have the Firestore emulator running otherwise you'll be importing your data back to prod. Be sure to check that Firestore is running by checking the web UI: http://localhost:4000. Start the emulator with: firebase emulators:start
exports.importFirestore = functions.https.onRequest(async (req, res) => {
const fs = require("fs");
let collection;
const fileName = "fs-export.json";
const exportedData = {};
exportedData[collection] = {};
fs.readFile(fileName, "utf8", async (err, data) => {
if (err) {
return console.log(err);
}
const arr = JSON.parse(data);
const batch = admin.firestore().batch();
for (let i in arr) {
collection = i;
for (let doc in arr[i]) {
if (arr[i].hasOwnProperty(doc)) {
const ref = admin.firestore().collection(collection).doc(doc);
batch.set(ref, arr[i][doc]);
} else {
console.log("Missing:", JSON.stringify(doc, null, 2));
}
}
}
await batch
.commit()
.then(() => {
return console.log("Import to Firestore Complete");
})
.catch(console.error);
return res.send("Import to Firestore Complete");
});
});
Solution 2:[2]
My answer is heavily inspired by @Geoffrey Bourne's answer, but I had to modify some stuff and figure out more details to get it working.
First, I upload the exportFirestore
to Cloud Functions (production). When I run it through this URL https://us-central1-<project-id>.cloudfunctions.net/exportFirestore
, I get a file DOWNLOADED, as Cloud Functions are read-only
The below code is for one collection named fl_content
, I'll consider expanding it to multiple collections
export const exportFirestore = functions.https.onRequest(async (req, res) => {
const collection = "fl_content";
const exportedData: any = {};
exportedData[collection] = {};
await admin
.firestore()
.collection(collection)
.get()
.then((snapshot) => snapshot.forEach((doc) => exportedData[collection][doc.id] = doc.data()))
.catch(console.error);
const data = JSON.stringify(exportedData);
res.setHeader('Content-disposition', 'attachment; filename=fire-export.json');
res.setHeader('Content-type', 'application/json');
res.write(data, function () {
res.end();
});
})
Once you have the file fire-export.json
downloaded, put it inside the functions
folder. Then open the URL for the import function (locally) http://localhost:5001/<project-id>/us-central1/importFirestore
. Make sure the collection
variable is the same in the export and import.
export const importFirestore = functions.https.onRequest(async (req, res) => {
const fs = require("fs");
const collection = "fl_content";
const fileName = "fire-export.json";
const exportedData: any = {};
exportedData[collection] = {};
fs.readFile(fileName, "utf8", async (err: any, data: any) => {
if (err) {
res.send(err);
functions.logger.error(err)
return;
}
const arr = JSON.parse(data);
const batch = admin.firestore().batch();
for (const i in arr) {
for (const doc in arr[i]) {
if (arr[i].hasOwnProperty(doc)) {
const ref = admin.firestore().collection(collection).doc(doc);
batch.set(ref, arr[i][doc]);
} else {
functions.logger.error("Missing:", JSON.stringify(doc, null, 2));
}
}
}
await batch
.commit()
.then(() => console.log("Import to Firestore Complete"))
.catch(console.error);
res.send("Import to Firestore Complete");
});
});
Solution 3:[3]
One way to persist your data is by exporting to directory on exit and on start import the same directory
firebase emulators:start --import=./mydir --export-on-exit mydir
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Geoffrey Bourne |
Solution 2 | Rami Alloush |
Solution 3 | griffins |