By using this site, you agree to the Privacy Policy and Terms of Use.
Accept
rocoderesrocoderes
  • Home
  • HTML & CSS
    • Login and Registration Form
    • Card Design
    • Loader
  • JavaScript
  • Python
  • Internet
  • Landing Pages
  • Tools
    • Google Drive Direct Download Link Generator
    • Word Count
  • Games
    • House Painter
Notification Show More
Latest News
How to set the dropdown value by clicking on a table row
Javascript – How to set the dropdown value by clicking on a table row
JavaScript
Attempting to increase the counter, when the object's tag exist
Javascript – Attempting to increase the counter, when the object’s tag exist
JavaScript
Cycle2 JS center active slide
Javascript – Cycle2 JS center active slide
JavaScript
Can import all THREE.js post processing modules as ES6 modules except OutputPass
Javascript – Can import all THREE.js post processing modules as ES6 modules except OutputPass
JavaScript
How to return closest match for an array in Google Sheets Appscript
Javascript – How to return closest match for an array in Google Sheets Appscript
JavaScript
Aa
Aa
rocoderesrocoderes
Search
  • Home
  • HTML & CSS
    • Login and Registration Form
    • Card Design
    • Loader
  • JavaScript
  • Python
  • Internet
  • Landing Pages
  • Tools
    • Google Drive Direct Download Link Generator
    • Word Count
  • Games
    • House Painter
Follow US
High Quality Design Resources for Free.
rocoderes > JavaScript > Javascript – Handle multiple Firebase Projects in the BackEnd Javascript
JavaScript

Javascript – Handle multiple Firebase Projects in the BackEnd Javascript

Admin
Last updated: 2023/12/13 at 5:44 PM
Admin
Share
10 Min Read
Handle multiple Firebase Projects in the BackEnd Javascript

Problem:

I have many apps in many projects and I want to get information about them to update them later, so I decide to create an API with NodeJS and I’m configuring the way my endpoint can retrieve information from different projects such as count the number of apps in the specific project, I know that I need the Service Account key for each project and I already download them so my core logic is an endpoint like this

Contents
Problem:Solution:
const { initProject } = require('../config/firebaseConfig')
async function getAllApps(req, res) {
  const projectId = req.params.projectId;
  const project = await initProject(projectId);

  try {
    androidApps = await project.projectManagement().listAndroidApps();
    iosApps = await project.projectManagement().listIosApps();

    const allApps = [...androidApps, ...iosApps];

    const appList = allApps.map((app) => ({
      appId: app.appId
    }));

    res.status(200).json(appList);

  } catch (error) {
    console.error('Error retrieving apps:', error);
    res.status(500).json({ error: 'Failed to retrieve apps for the project' });
  }
}

and the initProject is

const admin = require('firebase-admin');

// Initialize a project cache
const projectCache = new Map();

//initialize the project based on it's id name
async function initProject(projectId) {
    // Check if the project has already been initialized
    if (projectCache.has(projectId)) {
        return projectCache.get(projectId);
    }

    try {
        // Construct the path to the service account key file using the provided path or a default value
        if (process.env.FIREBASE_ACCOUNT_SERVICE_PATH !== null) {
            var serviceAccountPath = `${process.env.FIREBASE_ACCOUNT_SERVICE_PATH}\\${projectId}.json`;
        } else {
            var serviceAccountPath = `C:\\path\\to\\cred\\${projectId}.json`;
        }

        // Initialize the Firebase Admin SDK
        const serviceAccount = require(serviceAccountPath);

        admin.initializeApp({
            credential: admin.credential.cert(serviceAccount),
            databaseURL: `https://${projectId}.firebaseio.com`,
        });

        // Get the initialized Firebase project
        const firebaseProject = admin;

        // Cache the initialized project
        projectCache.set(projectId, firebaseProject);

        return firebaseProject;

    } catch (error) {

        console.error('Error initializing Firebase project: ', error);
        throw new Error('Failed to initialize Firebase project');
    }
}

module.exports = {
    initProject
}

the route is like this

router.get('/:projectId/apps', projectController.getAllApps);

so I start the express as well and the call for the first endpoint is this http://localhost:99/projects/project-name/apps so it works just fine my first call result in a success and I can receive what I need but if I try to call this endpoint again but with another project http://localhost:99/projects/project-name-2/apps it will fail because I already have initialize the firebase-admin without specify an app, but my need is exactly this, count the number of apps for each project for example, so I need to initialize it, I’ve tried to change this to kill the require as soon I get out of the firebaseConfig like this

// Initialize a project cache
const projectCache = new Map();

// Function factory to create a new Firebase App instance
function createFirebaseApp(projectId) {
    return () => {
        if (projectCache.has(projectId)) {
            const cachedProject = projectCache.get(projectId);
            return cachedProject;
        }

        try {
            // Construct the path to the service account key file using the provided path or a default value
            if (process.env.FIREBASE_ACCOUNT_SERVICE_PATH !== null) {
                var serviceAccountPath = `${process.env.FIREBASE_ACCOUNT_SERVICE_PATH}\\${projectId}.json`;
            } else {
                var serviceAccountPath = `C:\\EscolarManager\\Dados\\AppsCred\\${projectId}.json`;
            }

            // Initialize a new Firebase Admin SDK for each project
            const admin = require('firebase-admin');
            const serviceAccount = require(serviceAccountPath);

            const firebaseProject = admin.initializeApp({
                credential: admin.credential.cert(serviceAccount),
                databaseURL: `https://${projectId}.firebaseio.com`,
            });

            // Cache the initialized project
            projectCache.set(projectId, firebaseProject);

            return firebaseProject;
        } catch (error) {
            console.error('Error initializing Firebase project: ', error);
            throw new Error('Failed to initialize Firebase project');
        }
    }
}

module.exports = {
    createFirebaseApp
}

and I’ve tried to make a factory function too

const admin = require('firebase-admin');

// Initialize a project cache
const projectCache = new Map();

// Function factory to create a new Firebase App instance
function createFirebaseApp(projectId) {
    return () => {
        if (projectCache.has(projectId)) {
            const cachedProject = projectCache.get(projectId);
            return cachedProject;
        }

        try {
            // Construct the path to the service account key file using the provided path or a default value
            if (process.env.FIREBASE_ACCOUNT_SERVICE_PATH !== null) {
                var serviceAccountPath = `${process.env.FIREBASE_ACCOUNT_SERVICE_PATH}\\${projectId}.json`;
            } else {
                var serviceAccountPath = `C:\\EscolarManager\\Dados\\AppsCred\\${projectId}.json`;
            }

            // Initialize the Firebase Admin SDK without specifying an app name
            const serviceAccount = require(serviceAccountPath);

            const firebaseProject = admin.initializeApp({
                credential: admin.credential.cert(serviceAccount),
                databaseURL: `https://${projectId}.firebaseio.com`,
            });

            // Cache the initialized project
            projectCache.set(projectId, firebaseProject);

            return firebaseProject;
        } catch (error) {
            console.error('Error initializing Firebase project: ', error);
            throw new Error('Failed to initialize Firebase project');
        }
    }
}

// Example usage of the factory to create new Firebase App instances
const projectFactory = createFirebaseApp('project1');

const app1 = projectFactory();
const app2 = projectFactory();

console.log(app1 === app2); // This should print false, indicating they are different instances

but none of them works, same result. I’m caching the the project to not need to initialize the same project twice but call different projects is my issue here


The solution offered by ‘samthecodingman’ elegantly resolved the issue. I will now share the complete solution with a minor adjustment, which involves importing the service. I want to express my gratitude to him; thanks to his help, I now have a clearer understanding of this library. It functions in a manner similar to a set of functions, allowing me to simply call the functions and utilize the contents of its classes, such as the ProjectManagement class.

here is the controller

// Should return the list of apps id for this project
async function getAllApps(req, res) {
  const projectId = req.params.projectId;

  const project = getFirebaseAdminForProject(projectId);
  const projectManagement = getProjectManagement(project);

  try {
    androidApps = await projectManagement.listAndroidApps();
    iosApps = await projectManagement.listIosApps();

    const allApps = [...androidApps, ...iosApps];

    const appList = allApps.map((app) => ({
      appId: app.appId
    }));

    res.status(200).json(appList);

  } catch (error) {
    console.error('Error retrieving apps:', error);
    res.status(500).json({ error: 'Failed to retrieve apps for the project' });
  }
}

so the function he provided

const { initializeApp, getApps, cert } = require('firebase-admin/app');

function getFirebaseAdminForProject(projectId) {
    if (!projectId)
        throw new Error('Project ID is required!');

    let projectApp = getApps().find(app => app.name === projectId);

    if (projectApp)
        return projectApp;

    // if here, project is not yet initialized
    let serviceAccountPath = process.env.FIREBASE_ACCOUNT_SERVICE_PATH != null
        ? `${process.env.FIREBASE_ACCOUNT_SERVICE_PATH}\\${projectId}.json`
        : `C:\\path\\to\\${projectId}.json`;

    // Initialize the Firebase Admin SDK
    const serviceAccount = require(serviceAccountPath);

    return initializeApp({
        credential: cert(serviceAccount),
        databaseURL: `https://${projectId}.firebaseio.com`
    }, projectId); // <-- using projectId as instance name
}

module.exports = {
    getFirebaseAdminForProject
};

I guess I don’t need to save the project in a map variable anymore, it’s done internally in the firebase-admin (if I’m wrong you can comment here and explain what is being done exactly).

As you can see the solution he provide came with a very important information the “App” in the firebase-admin lib is about the admin app that is actually the project so it’s very important to understand this.
thank you samthecodingman!

the fix with the ‘!=’ ‘!==’ is also important. I can’t thank you enough!

Solution:

Based on your code, you may not be aware that the Firebase SDKs have the ability to initialize multiple app instances. When you call initializeApp without providing a name for the instance, you initialize the default instance. In your code above, you call admin.initializeApp(config) multiple times, and then try to store admin into your dictionary as the initialized project. Calling initializeApp for the same instance will throw an exception, and admin is just the SDK’s namespace, it does not represent the application you just initialized (the value returned from initializeApp is, as you realized in your second attempt).

Note: Only one instance of the Admin SDK is needed to communicate with all resources on that project, initializing multiple instances for the same project is redundant. An “app” here is not talking about iOS/Android/Web applications, but an initialized admin SDK instance.

Taking this into consideration, you can update your code to:

// ./firebase.js
const { initializeApp, getApps } = require('firebase-admin/app');

function getFirebaseAdminForProject(projectId) {
  if (!projectId)
    throw new Error('Project ID is required!');

  let projectApp = getApps().find(app => app.name === projectId);

  if (projectApp)
    return projectApp;

  // if here, project is not yet initialized
  let serviceAccountPath = process.env.FIREBASE_ACCOUNT_SERVICE_PATH != null // <- note != instead of !==
    ? `${process.env.FIREBASE_ACCOUNT_SERVICE_PATH}\\${projectId}.json`
    : `C:\\path\\to\\${projectId}.json`;

  return initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: `https://${projectId}.firebaseio.com`
  }, projectId); // <-- using projectId as instance name
}

exports.getFirebaseAdminForProject = getFirebaseAdminForProject;
exports.project1App = getFirebaseAdminForProject('project-1');
exports.project2App = getFirebaseAdminForProject('project-2');

To get Firestore for a given app, you can use:

const { getApp } = require('firebase-admin/app');
const { getFirestore } = require('firebase-admin/firestore');

const db = getFirestore(getApp('project-1'));

or

const { project1App } = require('./firebase');
const { getFirestore } = require('firebase-admin/firestore');

const db = getFirestore(project1App);

Consider switching out require/exports for the modern import/export equivalents.

Related

Subscribe to Our Newsletter

Subscribe to our newsletter to get our newest articles instantly!

Share this Article
Facebook Twitter Email Print
What do you think?
Love0
Sad0
Happy0
Sleepy0
Angry0
Dead0
Wink0
Previous Article Conditionally Show HTML with JavaScript After Input Checkbox Is Checked Javascript – Conditionally Show HTML with JavaScript After Input Checkbox Is Checked
Next Article Selecting a set of colors, making a new layer, and then sending to that layer using Illustrator script Javascript – Selecting a set of colors, making a new layer, and then sending to that layer using Illustrator script
Leave a comment Leave a comment

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

- Advertisement -

You Might Also Like

How to set the dropdown value by clicking on a table row

Javascript – How to set the dropdown value by clicking on a table row

February 11, 2024
Attempting to increase the counter, when the object's tag exist

Javascript – Attempting to increase the counter, when the object’s tag exist

February 11, 2024
Cycle2 JS center active slide

Javascript – Cycle2 JS center active slide

February 10, 2024
Can import all THREE.js post processing modules as ES6 modules except OutputPass

Javascript – Can import all THREE.js post processing modules as ES6 modules except OutputPass

February 10, 2024
rocoderesrocoderes
Follow US

Copyright © 2022 All Right Reserved By Rocoderes

  • Home
  • About us
  • Contact us
  • Disclaimer
Join Us!

Subscribe to our newsletter and never miss our latest news, podcasts etc.

Zero spam, Unsubscribe at any time.
Welcome Back!

Sign in to your account

Lost your password?