Firebase Admin SDK global app initialization in Node.js
Asked Answered
G

4

10

I am building an Express.js app, using the Firebase Admin SDK for several features such as ID Token validation and Cloud Firestore access. In my main app.js file, I am initializing the app as:

const admin = require('firebase-admin')

const serviceAccount = require('../config/account-credentials.json')
admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: 'https://databaseurl.firebaseio.com'
})

In another file, all I'm doing is importing:

const admin = require('firebase-admin')

I am able to call admin.auth().verifyIdToken and verify ID tokens just fine. However when I call app.database(), it complains that the app is never initialized. Inititalizing the app again creates a new error saying:

The default Firebase app already exists. This means you called initializeApp() more than once without providing an app name as the second argument. In most cases you only need to call initializeApp() once. But if you do want to initialize multiple apps, pass a second argument to initializeApp() to give each app a unique name.

Do I need to create multiple apps with different names for this to work? Or how can I use one app throughout the project.

Gabriella answered 14/1, 2020 at 2:16 Comment(1)
Could you edit the question and provide a MCVE (as opposed to just a few lines)? Please read this: stackoverflow.com/help/minimal-reproducible-exampleVishnu
N
11

You should initialize your application exactly once and then re-use that reference. There are various ways to do this, but the way I prefer is to import firebase.ts (which initializes the Firebase application and services) into my index.ts (or whatever your entry point is). Then I pass a reference to any other files that need a particular service. I'm using TypeScript, so adjust this as needed if you're using vanilla JS.

firebase.ts

import * as admin from 'firebase-admin';

// Initialize our project application
admin.initializeApp();

// Set up database connection
const firestoreDb: FirebaseFirestore.Firestore = admin.firestore();
firestoreDb.settings({ timestampsInSnapshots: true });
export const db = firestoreDb;

My index.ts file will import it:

import { db } from './firebase';

Then when I set up my routes with Express, I'll have each route in another file with its own function. Then pass in the db reference to any that need it.

  app
    .route('events')
    .get((req: Request, res: Response) => {
      get_events(db, res);
      return;
    });

Here is a blog post where I explain it a bit more:

https://medium.com/@jasonbyrne/how-to-structure-a-serverless-rest-api-with-firebase-functions-express-1d7b93aaa6af

If you don't like the dependency injection method or prefer to lazy-load only the services you nee, you could go another it a different way. In that method you'd have your firebase.js file (or whatever you call it) that you import to any pages that need it and call a function to load that service. Here I'm just doing Firestore, but you could create similar functions for references to other services.

Just typed this up as a sample...

import * as admin from 'firebase-admin';

// Initialize our project application
admin.initializeApp();

// Database reference, not yet loaded
let db: FirebaseFirestore.Firestore | null = null;

// Get cached db reference or create it
export function getDatabaseReference() {
    if (db === null) {
        db = admin.firestore();
    }
    return db;
}

I hope this helps. Let me know if you have any questions.

Nominative answered 14/1, 2020 at 3:56 Comment(2)
cannot use import outside module... how does one resolve thatTacmahack
Thanks for the detailed explanation, but I still can't find official documentation that says to not call admin.firestore() multiple times. Basically the SDK could do what you suggested under the hood already?Chat
W
3

here's the full typescript code to initialize modular firebase admin SDK in any node.js server:

import { credential } from "firebase-admin";
import { initializeApp, getApps, getApp } from "firebase-admin/app";
import { getAuth } from "firebase-admin/auth";
import {
  FIREBASE_CLIENT_EMAIL,
  NEXT_PUBLIC_FIREBASE_DATABASE_URL,
  NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  FIREBASE_PRIVATE_KEY,
} from "../common/envVar";

const adminCredentials = {
  credential: credential.cert({
    projectId: NEXT_PUBLIC_FIREBASE_PROJECT_ID,
    clientEmail: FIREBASE_CLIENT_EMAIL,
    privateKey: JSON.parse(FIREBASE_PRIVATE_KEY),
  }),
  databaseURL: NEXT_PUBLIC_FIREBASE_DATABASE_URL,
};

// avoid initializing twice
const firebaseAdminApp =
  getApps().length === 0 ? initializeApp(adminCredentials) : getApp();

export const authAdmin = getAuth(firebaseAdminApp);


here's how to use it:

import { authAdmin } from "../server/initFirebaseAdmin";
const uid = (await authAdmin.verifyIdToken(token)).uid;
Williamson answered 18/3, 2023 at 16:36 Comment(0)
W
2

I got this working very nicely in a microservice API in cloudRun using global.

const admin = require('firebase-admin');
global.GeoFirestore = require('geofirestore').GeoFirestore;

admin.initializeApp({
  credential: admin.credential.applicationDefault()
});

global.db = admin.firestore();
global.admin = admin;

In another module I can access collections:

var docRef = db.collection("collection").doc(doc.id);
    docRef.update({state}).then((doc) => {
        console.log(doc)
    }).catch((error) => {
        console.log("Error getting document:", error);
    });

For working on a GeoFirestore GeoPoint I needed to have admin globally:

const geofirestore = new GeoFirestore(db);
const geocollection = geofirestore.collection('bin');
var objectToBeStored = {
   ...data,
   coordinates: new admin.firestore.GeoPoint(coordinates.lat, coordinates.lng)
}
geocollection.add(objectToBeStored ).then((docRef) => {
    console.log(`added data: ${docRef.id}`);
}).catch((error) => {
    console.log(`error: ${error}`);
})
Windflower answered 12/9, 2020 at 12:47 Comment(0)
B
1

Here's how I did it: I created a wrapper around the admin.firestore() etc functions I needed, and I import that and use it in all my other functions:

FunctionsShared.ts

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';

admin.initializeApp(functions.config().firebase);

export const FunctionsShared = {
  firestore: () => admin.firestore()
};

MyFirebaseFunction.ts

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import {FunctionsShared} from './FunctionsShared';

export getStaleDocumentsQuery = functions.https.onCall(() => {
  // Use FunctionsShared.firestore() instead of admin.firestore()
  return FunctionsShared.firestore()
    .collection('mirror')
    .where('updated', '<', oneMonthAgo)
    .orderBy('updated')
    .limit(limit);
}

Broadnax answered 24/12, 2021 at 1:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.