Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Firebase Admin SDK global app initialization in Node.js

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.

like image 465
Sam Schmitt Avatar asked Jan 14 '20 02:01

Sam Schmitt


2 Answers

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.

like image 108
Jason Byrne Avatar answered Sep 20 '22 12:09

Jason Byrne


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}`);
})
like image 35
ASomN Avatar answered Sep 19 '22 12:09

ASomN