Firebase Admin SDK global app initialization in Node.js - 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.

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.

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}`);
})

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);
}

Related

Cloud Functions Firestore import doesn't work

I'm trying to use Firestore from Cloud functions package like this:
export async function deleteDocRecursively(docRef) {
await Firestore.recursiveDelete(docRef);
}
This works:
const {Firestore} = require('#google-cloud/firestore');
This doesn't work:
import {Firestore} from '#google-cloud/firestore';
I get this error:
TS2339: Property 'recursiveDelete' does not exist on type 'typeof Firestore'.
https://www.npmjs.com/package/#google-cloud/firestore
As mentioned in the documentation, recursiveDelete is a method on an instance of Firestore class and not a static method on the class itself. Try:
import { Firestore } from '#google-cloud/firestore';
// const { Firestore } = require('#google-cloud/firestore');
const db = new Firestore();
export async function deleteDocRecursively(docRef) {
await db.recursiveDelete(docRef); // <-- not 'Firestore'
}
You are using client firestore inside firebase functions instead you should use the one which comes with admin one.
As you are using client firestore
const {Firestore} = require('#google-cloud/firestore');
Above one is working and
import {Firestore} from '#google-cloud/firestore';
Is being failed and getting below error
Property 'recursiveDelete' does not exist on type 'typeof Firestore'.
To get it work you have to use the admin one as this will run on the server.
As Firebase client SDK is meant to run in a client-side environment hence we use Client SDK using firebaseConfiguration listed in the firebase console.
While in firebase functions by initializing the Firebase Admin SDK with admin.initializeApp(), we can perform actions on behalf of your users and take advantage of the security and management features provided by Firebase.
When using firebase function it is advisable to use admin services as stated in the docs
If you have configured your firebase functions with typescript then follow this:
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
admin.initializeApp();
const firestore = admin.firestore();
exports.deleteDocRecursively = functions.https.onRequest(async (req, res) => {
// Delete Recursively
const documentRef = firestore.collection("users").doc("<doc_id of doc to be deleted>");
await firestore.recursiveDelete(documentRef);
});
If you have configured your firebase functions with javascript follow this:
const = functions require("firebase-functions");
const = admin require("firebase-admin");
admin.initializeApp();
const firestore = admin.firestore();
exports.deleteDocRecursively = functions.https.onRequest(async (req, res) => {
// Delete Recursively
const documentRef = firestore.collection("users").doc("<doc_id of doc to be deleted>");
await firestore.recursiveDelete(documentRef);
});
For more information go through these links:
Thread using recursiveDelete

Error: Can't determine Firebase Database URL

I need to execute cloud functions on a database that is not the default one, but when I try to load it I get this error:
your textError: Can't determine Firebase Database URL.
This is my code:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase);
const app2 = admin.initializeApp(
{
databaseUrl: "{my_db_url}",
}, "app2");
exports.MyFunction = functions.pubsub.schedule("* * * * *") .timeZone("Europe/Rome") .onRun((context) => {
...
const database = admin.database(app2);
...
});
I cannot find any solution in the documentation.
I tried to look in the documentation or for people with the same problem, but I didn't find any solution.
In the databaseUrl, I'm pasting the database url I copy from the Firebase console.

I need to index firebase data to algolia , rules in firebase for both read and write requires Authentication (auth!=null)

I am using the code described in the algolia docs. It is working when firebase rules allow both read and write without authentication. This does not work with data which requires authentication. What I can do to add auth in below code ?
I tried using firebase-admin, I think this method will work only when rule is changed to allow read for a single uid.
const algoliasearch = require('algoliasearch');
const dotenv = require('dotenv');
const firebase = require('firebase');
const admin = require("firebase-admin");
var serviceAccount = require("./config/serviceAccountKey.json");
// load values from the .env file in this directory into process.env
dotenv.config();
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: process.env.FIREBASE_DATABASE_URL
});
firebase.initializeApp({
databaseURL: process.env.FIREBASE_DATABASE_URL
});
admin
.auth()
.createCustomToken('siddharth')
.then((customToken) => {
console.log(customToken);
firebase.auth().authenticateWithCustomToken(customToken);
})
.catch((error) => {
console.log('Error creating custom token:', error);
});
// admin.auth.createCustomToken('siddharth').then(token => {
// });
const database = firebase.database();
const algolia = algoliasearch(
process.env.ALGOLIA_APP_ID,
process.env.ALGOLIA_API_KEY
);
const index = algolia.initIndex(process.env.ALGOLIA_INDEX_NAME);
database.ref('/questions').once('value', questions => {
const records = [];
questions.forEach(question => {
// get the key and data from the snapshot
const childKey = question.key;
const childData = question.val();
// We set the Algolia objectID as the Firebase .key
childData.objectID = childKey;
// Add object for indexing
records.push(childData);
});
console.log(records);
// Add or update new objects
index
.saveObjects(records)
.then(() => {
console.log('questions imported into Algolia');
})
.catch(error => {
console.error('Error when importing question into Algolia', error);
process.exit(1);
});
});
Since this seems to be a Node.js script that accesses Firebase Authentication by using service credentials with the Admin SDK, you can also use that Admin SDK to access the database. Accessing a Firebase service through the Admin SDK with service credentials gives full administrative access, and bypasses any security rules you may have configured for your database.
In code, change:
const database = firebase.database();
To:
const database = admin.database();

Express JS: Send Databse as Variable / Parameter while requiring a module

Say I have the following code
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const serviceAccount = require("./permissions.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://blah-blah-blah.firebaseio.com"
});
const db = admin.firestore();
app.use("/auth", require("./auth"));
Now the problem is that I have to use the cloud firestore databse functionality, which is curently stored in the variable db, in my auth.js file as well, without using admin.initializeApp a second time. Is there any way to acomplish that, like importing/exporting the db variable etc.
This is where I am using it in auth.js:
const express = require("express");
const auth = express.Router();
db.collection("data")
.doc("sample")
.get()
.then(
document => {
if (document.exists) {
return done(null, {
id: user.id,
name: user.name,
email: user.email
});
} else {
//create document
}
},
err => {
//handle error
}
);
Of course, right now db will be undefined, which is the problem I need to tackle
Since version 1.0.0 of the Firebase SDK for Cloud Functions you have to initialize with admin.initializeApp();, see the doc here.
Then, to interact with Firestore, you just need to use the Admin SDK, for example as follows:
admin.firestore().collection('....').get();
So, if I understand correctly your question, the following changes should do the trick:
index.js
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
//const db = admin.firestore(); not sure you need that here, it depends if you interact (or not) with Firestore in index.js
app.use("/auth", require("./auth"));
auth.js
const admin = require("firebase-admin");
const db = admin.firestore();
//....
db.collection("data")
.doc("sample")
.get()
.then(
document => {
if (document.exists) {
return done(null, {
id: user.id,
name: user.name,
email: user.email
});
} else {
//create document
}
},
err => {
//handle error
}
);
//....

Writing unit tests for services in feathers without using a database

I would like to write some unit tests for feathers services.
I want this test to run completely independent, which means i do not want to use the database.
This is an example snippet of my service which is using sequelize:
src/services/messages/messages.service.js
// Initializes the `messages` service on path `/messages`
const createService = require('feathers-sequelize');
const createModel = require('../../models/messages.model');
const hooks = require('./messages.hooks');
const filters = require('./messages.filter');
module.exports = function (app) {
const app = this;
const Model = createModel(app);
const paginate = app.get('paginate');
const options = {
name: 'messages',
Model,
paginate
};
// Initialize our service with any options it requires
app.use('/messages', createService(options));
// Get our initialized service so that we can register hooks
const service = app.service('messages');
service.hooks(hooks);
if (service.filter) {
service.filter(filters);
}
};
I would maybe try to mock the database with the library sequelize-test-helpers but I am not sure how this would work in combination with feathers.
This is how my current test in typescript for this service looks like:
src/test/services/messages.test.ts
import assert from 'assert';
import { app } from '../../src/app';
describe('\'messages\' service', () => {
before(() => {
// maybe add an entry to the mocked database
});
after(() => {
// maybe delete that entry
});
it('registered the service', () => {
const service = app.service('messages');
assert.ok(service, 'Registered the service');
});
it('returns a single record', async () => {
// get result with id 1 (maybe added item in before-hook)
const res = await service.get(1);
should().exist(res);
res.should.be.a('object');
// more checks...
});
});
The first 'it(...)' was generated by feathers itself and the second 'it(...)' shows the functionality I want the test to have.
But the problem is that I am not sure how to write this test so that the service will not use the original database.
Does anybody of you have an idea how I could write a test for a feathers service without using the actual database?
Thanks in advance!
Set environment to TEST and in config set the database on the test.json . As seen here : https://docs.feathersjs.com/guides/basics/testing.html#test-database-setup

Resources