Firebase Firestore is one of the most popular NoSQL cloud databases for Android, iOS, Flutter, Unity, and web applications. It offers real-time synchronization, automatic scaling, offline support, and a serverless architecture that makes app development fast and simple.
However, as applications grow, many developers reach a point where Firestore is no longer the ideal database solution. Complex reporting, SQL joins, business analytics, legacy software integration, and lower hosting costs often make MySQL a better choice.
Migrating from Firestore to MySQL may seem challenging because the two databases use completely different data models. Firestore stores documents inside collections, while MySQL organizes data into structured tables with rows, columns, primary keys, and relationships.
Fortunately, with the help of the Firebase Admin SDK and Node.js, you can export Firestore data, transform it into a relational format, and import it into MySQL efficiently.
In this tutorial, you’ll learn how to migrate your Firebase Firestore database to MySQL step by step, including database design, data conversion, migration scripts, handling nested documents, and validating the migration.
Why Migrate from Firestore to MySQL?
Firestore is excellent for modern mobile and web applications, but there are scenarios where a relational database provides significant advantages.
Common Reasons to Migrate
- Need complex SQL queries
- Generate business reports
- Perform JOIN operations
- Reduce long-term database costs
- Integrate with existing ERP or CRM systems
- Use legacy PHP or Java applications
- Maintain structured relational data
- Host databases on your own VPS
Firestore vs MySQL
| Feature | Firestore | MySQL |
|---|---|---|
| Database Type | NoSQL | Relational SQL |
| Schema | Flexible | Fixed |
| Relationships | Manual | Native |
| JOIN Queries | Not Supported | Supported |
| Transactions | Limited | Advanced |
| Hosting | Cloud Only | Self-host or Cloud |
| Offline Support | Built-in | Application Level |
| Best For | Realtime Apps | Business Systems |
When Should You Migrate?
Migrating databases is a significant decision. Consider moving to MySQL if your application has outgrown Firestore’s document model or requires advanced relational features.
Typical migration scenarios include:
- E-commerce platforms with complex order relationships
- School or college management systems
- Hospital management software
- Inventory management applications
- Accounting and financial systems
- Large reporting dashboards
- Enterprise applications
If your application primarily benefits from real-time synchronization and flexible document storage, Firestore may still be the better choice.
Prerequisites
Before starting the migration, ensure you have the following tools installed:
- Node.js 18 or later
- MySQL Server 8+
- Firebase Project
- Firebase Admin SDK
- MySQL Workbench (optional)
- Visual Studio Code
Verify your installations:
node -v
npm -v
mysql --version
Create a New Node.js Project
Create a folder for the migration project:
mkdir firestore-mysql-migration
cd firestore-mysql-migration
npm init -y
Install the required packages:
npm install firebase-admin mysql2 dotenv
Your project structure should look like this:
firestore-mysql-migration/
├── node_modules/
├── serviceAccount.json
├── index.js
├── package.json
├── .env
└── database.js
Create a Firebase Service Account
The Firebase Admin SDK requires a service account key to securely access your Firestore database.
- Open the Firebase Console.
- Select your project.
- Go to Project Settings.
- Open the Service Accounts tab.
- Click Generate New Private Key.
- Download the JSON file.
- Save it as
serviceAccount.jsonin your project folder.
Important: Never commit this file to GitHub or any public repository.
Initialize Firebase Admin SDK
Create an index.js file and initialize Firebase:
const admin = require("firebase-admin");
const serviceAccount = require("./serviceAccount.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
const db = admin.firestore();
This establishes a secure connection to your Firestore database.
Create the MySQL Database
Log into MySQL:
mysql -u root -p
Create a new database:
CREATE DATABASE firebase_migration;
Select the database:
USE firebase_migration;
Example Firestore Collection
Suppose your Firestore contains the following users collection:
{
"name": "John",
"email": "john@example.com",
"age": 30,
"country": "USA"
}
In Firestore, every document has a unique document ID that should also be stored in MySQL to maintain references.
Create the Equivalent MySQL Table
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
firebase_id VARCHAR(100),
name VARCHAR(100),
email VARCHAR(150),
age INT,
country VARCHAR(100)
);
This relational structure stores both the Firestore document ID and the document fields.
Configure the MySQL Connection
Create a database.js file:
const mysql = require("mysql2/promise");
const connection = mysql.createPool({
host: "localhost",
user: "root",
password: "your_password",
database: "firebase_migration"
});
module.exports = connection;
Using a connection pool improves performance when importing thousands of records.
Store Credentials Securely
Create a .env file:
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=your_password
DB_NAME=firebase_migration
Then load the environment variables:
require("dotenv").config();
Avoid hardcoding database credentials directly into your application. Environment variables make your project more secure and easier to configure across different environments.
What’s Next?
Your project is now fully configured:
- Node.js project created
- Firebase Admin SDK installed
- MySQL database created
- Connection pool configured
- Service account connected
- Ready to export Firestore data
In Part 2, you’ll learn how to:
- Read Firestore collections
- Export documents
- Convert Firestore data into SQL format
- Insert records into MySQL
- Handle nested objects and arrays
- Migrate subcollections
- Optimize imports with batch processing
Reading Data from Firestore
Now that the project has been configured, it’s time to read data from Firestore and insert it into MySQL.
The Firebase Admin SDK allows server-side applications to access every document in your Firestore database without client-side restrictions.
Import Required Packages
Open index.js and import the required modules.
require("dotenv").config();
const admin = require("firebase-admin");
const mysql = require("./database");
const serviceAccount = require("./serviceAccount.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
const db = admin.firestore();
Read an Entire Firestore Collection
Suppose your Firestore contains a collection called users.
You can retrieve every document using:
async function getUsers() {
const snapshot = await db.collection("users").get();
snapshot.forEach(doc => {
console.log(doc.id);
console.log(doc.data());
});
}
getUsers();
Each document contains:
- Document ID
- Document fields
- Timestamps
- Nested objects
Example Firestore Documents
{
"name":"John",
"email":"john@example.com",
"age":28,
"country":"USA"
}
{
"name":"Sarah",
"email":"sarah@gmail.com",
"age":24,
"country":"Canada"
}
Insert Documents into MySQL
Create a migration function.
async function migrateUsers(){
const snapshot = await db.collection("users").get();
for(const document of snapshot.docs){
const user = document.data();
await mysql.execute(
`INSERT INTO users
(firebase_id,name,email,age,country)
VALUES(?,?,?,?,?)`,
[
document.id,
user.name,
user.email,
user.age,
user.country
]
);
console.log("Imported:",user.name);
}
}
migrateUsers();
Run the script:
node index.js
Your Firestore documents will now be inserted into MySQL.
Handling Missing Fields
Firestore documents don’t always contain identical fields.
Example:
{
"name":"Alex"
}
Instead of causing SQL errors, provide default values.
await mysql.execute(
`INSERT INTO users
(firebase_id,name,email,age,country)
VALUES(?,?,?,?,?)`,
[
document.id,
user.name || "",
user.email || "",
user.age || 0,
user.country || ""
]
);
Mapping Firestore Data Types
| Firestore | MySQL |
|---|---|
| String | VARCHAR |
| Integer | INT |
| Double | DOUBLE |
| Boolean | TINYINT(1) |
| Timestamp | DATETIME |
| Array | JSON |
| Map | JSON |
| GeoPoint | Latitude + Longitude |
Convert Firestore Timestamp
Firestore stores timestamps differently from MySQL.
{
createdAt: Timestamp(...)
}
Convert before inserting.
const createdAt =
user.createdAt
? user.createdAt.toDate()
: new Date();
Insert normally:
await mysql.execute(
`INSERT INTO users
(created_at)
VALUES(?)`,
[createdAt]
);
Handling Arrays
Firestore arrays cannot be stored directly inside standard SQL columns.
Example Firestore:
{
skills:[
"Java",
"Kotlin",
"Firebase"
]
}
Option 1 (Recommended)
Store as JSON.
JSON.stringify(user.skills)
SQL
skills JSON
Handling Nested Objects
Firestore:
{
address:{
city:"Dubai",
country:"UAE"
}
}
Option 1
Store JSON
JSON.stringify(user.address)
Option 2
Flatten
city
country
Usually flattening is better for searching.
Migrating Multiple Collections
Most Firebase projects contain more than one collection.
Example
users
orders
products
payments
notifications
Instead of duplicating code, create a reusable function.
async function migrateCollection(collectionName){
const snapshot=
await db.collection(collectionName).get();
console.log(
snapshot.size
);
}
Call
migrateCollection("users");
migrateCollection("products");
migrateCollection("orders");
Migrating Subcollections
Firestore supports nested collections.
Example
users
|
|---orders
|
|---messages
|
|---addresses
Retrieve them.
const users=
await db.collection("users").get();
for(const user of users.docs){
const orders=
await user.ref
.collection("orders")
.get();
}
Each order should contain
- Firebase User ID
- Order ID
- Order Data
This becomes a relational table.
Create Orders Table
CREATE TABLE orders(
id INT AUTO_INCREMENT PRIMARY KEY,
firebase_order_id VARCHAR(100),
firebase_user_id VARCHAR(100),
amount DOUBLE,
status VARCHAR(50)
);
Import Subcollection
await mysql.execute(
`INSERT INTO orders
(firebase_order_id,
firebase_user_id,
amount,
status)
VALUES(?,?,?,?)`,
[
order.id,
user.id,
data.amount,
data.status
]
);
Using Batch Inserts
Instead of
Insert
Insert
Insert
Insert
Insert hundreds together.
Example
const values=[];
snapshot.forEach(doc=>{
const u=doc.data();
values.push([
doc.id,
u.name,
u.email,
u.age,
u.country
]);
});
SQL
await mysql.query(
`INSERT INTO users
(firebase_id,
name,
email,
age,
country)
VALUES ?`,[values]
);
Batch inserts can improve migration performance dramatically when dealing with large datasets.
Error Handling
Always wrap migration code inside a try-catch block.
try{
await migrateUsers();
}
catch(error){
console.log(error);
}
Better logging.
catch(error){
console.error(
"Migration Failed",
error.message
);
}
Progress Indicator
Display migration progress.
let count=0;
snapshot.forEach(()=>{
count++;
console.log(
`Migrated ${count}`
);
});
Useful when importing thousands of records.
Large Database Tips
For databases containing hundreds of thousands of documents:
- Use pagination.
- Import one collection at a time.
- Use batch inserts.
- Disable indexes during import if appropriate.
- Create indexes after migration.
- Run the migration on a server instead of a local machine.
- Back up both Firestore and MySQL before starting.
Summary
At this stage, you’ve learned how to:
- Read Firestore collections
- Connect to MySQL
- Convert Firestore documents into SQL records
- Handle timestamps, arrays, and nested objects
- Migrate subcollections
- Improve performance using batch inserts
- Add error handling and progress logging
Verify the Migration
After migrating your Firestore data to MySQL, don’t immediately switch your application to the new database. The first step is to verify that every document has been migrated successfully and accurately.
A proper verification process helps identify missing records, duplicate entries, incorrect data types, or conversion errors before they affect production users.
1. Compare Record Counts
Count documents in Firestore:
const snapshot = await db.collection("users").get();
console.log(snapshot.size);
Count rows in MySQL:
SELECT COUNT(*) FROM users;
Both values should match.
2. Compare Sample Records
Randomly compare multiple records from both databases.
Check:
- Name
- Phone Number
- Timestamps
- Status
- Nested Data
3. Validate Relationships
If you migrated subcollections into relational tables, verify that every foreign key references the correct parent record.
Example:
SELECT *
FROM orders
WHERE firebase_user_id='abc123';
Testing Your Application
Before removing Firestore completely, update your application to use MySQL in a staging environment.
Test:
- User Login
- User Registration
- CRUD Operations
- Search
- Reports
- Dashboard
- Notifications
- Payment Flow
Only move to production after all features work correctly.
Performance Optimization
Create Indexes
Indexes dramatically improve query performance.
Example:
CREATE INDEX idx_email
ON users(email);
Index Foreign Keys
CREATE INDEX idx_user
ON orders(firebase_user_id);
Avoid SELECT *
Instead of:
SELECT *
FROM users;
Use:
SELECT
name,
email
FROM users;
Fetching only the required columns reduces memory usage and speeds up queries.
Use Pagination
Instead of loading every row:
SELECT *
FROM users
LIMIT 50 OFFSET 0;
Common Migration Problems
Duplicate Records
Cause:
Running the migration script multiple times.
Solution:
- Use unique constraints.
- Use UPSERT statements.
- Check existing records before inserting.
Timestamp Errors
Cause:
Firestore timestamps are not automatically converted to MySQL DATETIME values.
Solution:
timestamp.toDate();
NULL Values
Some Firestore documents may not contain every field.
Example:
{
"name":"John"
}
Always provide default values before inserting data.
Encoding Problems
If your application stores multilingual text, emojis, or special characters, configure MySQL to use UTF-8.
ALTER DATABASE firebase_migration
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
Memory Issues
Avoid loading very large collections into memory at once.
Instead:
- Read documents in batches.
- Process collections separately.
- Release unused objects.
Production Best Practices
- Backup Firestore before migration.
- Backup MySQL before importing.
- Test on a staging server first.
- Use transactions where appropriate.
- Log every migration step.
- Validate imported data.
- Create indexes after bulk imports.
- Monitor server resources during migration.
Migration Checklist
| Task | Status |
|---|---|
| Backup Firestore | ✅ |
| Create MySQL Database | ✅ |
| Install Firebase Admin SDK | ✅ |
| Configure Node.js | ✅ |
| Export Firestore Data | ✅ |
| Create SQL Tables | ✅ |
| Import Data | ✅ |
| Verify Records | ✅ |
| Test Application | ✅ |
| Go Live | ✅ |
Frequently Asked Questions
Can I migrate Firestore without downtime?
Yes. The safest approach is to migrate data while the application is still using Firestore, then briefly pause writes, perform a final synchronization, and switch the application to MySQL.
Can I migrate only one collection?
Yes. Firestore collections are independent, allowing you to migrate individual collections without affecting others.
Can nested documents be stored in MySQL?
Yes. You can either flatten nested fields into separate columns, store them in JSON columns, or normalize them into separate relational tables depending on your application’s needs.
How long does migration take?
It depends on the size of your database, server resources, network speed, and the complexity of your data. Small databases may take only a few minutes, while larger datasets with millions of documents can take several hours.
Should I delete Firestore after migration?
No. Keep Firestore available until you’ve confirmed that your MySQL-backed application is stable and all required data has been verified.
Final Verdict
Migrating from Firebase Firestore to MySQL allows developers to take advantage of relational data models, SQL queries, advanced reporting, and self-hosted infrastructure. While Firestore excels at real-time synchronization and flexible document storage, MySQL is often a better fit for enterprise applications, analytics platforms, and systems with complex relationships.
By using the Firebase Admin SDK together with Node.js, you can export Firestore collections, transform documents into relational records, and import them into MySQL with minimal manual effort. Careful planning, proper schema design, and thorough testing are essential for a successful migration.
If you’re planning to scale your application or integrate with traditional business systems, learning how to migrate from Firestore to MySQL is a valuable skill that can make your applications more flexible and easier to manage over the long term.


