Having trouble connecting to my Firebase database - Node & Express - javascript

I am still learning programming in general so sorry if I don't make sense.
I am trying to connect to my firebase database but I get a PERMISSION_DENIED error. The database in my firebase is set to Test mode so anyone should be able to access it.
I have added all the npm packages needed based on the firebase docs as well.
Let me know if I need to provide more information.
I am not sure what I am doing wrong here. Would anyone know? Any help is appreciated.
Here is my module file
var express = require('express');
var firebase = require('firebase');
// Initialize Firebase
var config = {
apiKey: "apikey",
authDomain: "authdomain",
databaseURL: "databaseurl",
storageBucket: "storagebucket"
};
firebase.initializeApp(config);
var db = firebase.database();
var ref = db.ref("/users");
ref.on("value", function(snapshot) {
console.log(snapshot.val());
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
});
var index = require('./routes/index');
app.use('/', index);
module.exports = app;
Here is my routes/index.js
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'The Title' });
});
module.exports = router;

Asynchronous listeners: Data stored in a Firebase Realtime Database is retrieved by attaching an asynchronous listener to a
database reference. The listener is triggered once for the initial
state of the data and again anytime the data changes. An event
listener may receive several different types of events.
Helpful link https://firebase.google.com/docs/database/admin/retrieve-data
You need to create a reference variable that corresponds to your database path
var ref = db.ref("server/saving-data/fireblog/posts");
and then you'll attach an asynchronous callback to read the data at the reference
ref.on("value", function(snapshot) {
console.log(snapshot.val());
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
});
I believe your issue with PERMISSION_DENIED is that you're using
var db = firebase.database();
instead of
var db = admin.database();

So I figured out a way to properly connect to my firebase database. I am not sure if this is the best way but the first thing I did was delete the current database and recreated it (not sure if this helped or was needed but I just wanted a fresh install just in-case something was wrong before.)
Then inside my database dashboard in firebase I went to the "Gear Icon => Project Settings" next to "Project Overview" header on the upper left of the dashboard screen. From here under Firebase Admin SDK I clicked "Generate New Private Key" button on the bottom.
This gave me a .json file which was downloaded onto my computer. I changed the downloaded files name to something more simple like myfirstapp-firebase-db.json. I added this file into the folder where my node js is being stored.
Before I started writing the code to connect to my firebase database, I had to make sure that my "Database => Rules" were set to true for read & write privileges in my firebase project.
Database Rules setup:
{
"rules": {
".read": true,
".write": true
}
}
After everything needed to configure firebase was taken care of, I simply configured my module.js file to properly connect. Below is the code necessary to connect to firebase. The two important things were "serviceAccount" and "databaseURL".....
var firebase = require('firebase');
// Initialize firebase
firebase.initializeApp({
serviceAccount: "./<your-service-account-url>-firebase-db.json",
databaseURL: "https://<your-database-url>.firebaseio.com/"
});
var db = firebase.database();
var ref = db.ref("/users");
..... The "serviceAccount" is a route to the .json file downloaded from the step "Generate New Private Key" above. You can find the databaseURL inside the "Database" dashboard on the top of the white block inside firebase. I simply copied and pasted that url into the databaseURL.
Then I put firebase.database() into a var = db and then specified the ref.
At this point my connection was successful and when I did a node modules.js it showed me in the console everything that is in the ref database. Make sure to have some pre populated fields in the database for the console to show you all the items inside. I hope this may be helpful to someone and if anyone knows of a better way of doing this I would love to know your suggestions!

Related

Create multiple Firebase Instances for the same project in Node.js

I have a Node.js server, inside which I want to have two firebase instances.
One instance should use the JavaScript SDK and will be used to provide authentication - login/register. The other instance should use the Admin SDK and will be used to read/write from the Realtime Database. I want to use this approach, so that I don't have to authenticate the user before each request to the Realtime DB.
I've read how we're supposed to initialize Firebase instances for multiple projects, but I'm not sure if my issue isn't coming from the fact that both instances are for the same project.
My issue is that I can use the JS SDK without any issue and I can login/register the user, but for some reason I can't get the Admin SDK to work.
Here's how I'm instantiating the apps:
const admin = require("firebase-admin");
const { applicationDefault } = require('firebase-admin/app');
admin.initializeApp({
credential: applicationDefault(),
databaseURL: 'my-database-url'
}, 'adminApp');
const firebase = require("firebase/app");
firebase.initializeApp(my-config);
Now I can use the JS SDK without an issue, but not the Admin SDK. I've created a test endpoint to just get data from my Realtime DB:
app.get("/api/test", (req, res) => {
const uid = 'my-user-UID';
admin.database().ref(`users/${uid}`)
.once('value', (snapshot) => {
if(snapshot) {
console.log('data');
} else {
console.log('no data');
}
});
});
Now here as an approach to getting the data from the Realtime DB, I tried all possible described approaches. Using get with child and all sorts of possible combinations. Here's an example of another approach I used:
get(child(ref(admin.database()), `users/${uid}`)).then((snapshot) => {
if (snapshot.exists()) {
// retrieved data
} else {
// No data
}
}).catch((error) => {
console.error(error);
});
For the first approach I wasn't getting any response at all, like the once wasn't executing. For the second one I think I was getting - typeerror: pathstring.replace is not a function firebase. At some point I was getting a no firebase app '[default]' has been created . These errors don't worry me as much, but since I saw the last error I moved my focus to the initialization of the apps, but still to no avail.
I just need a direction of where my issue might be coming from.
Update:
The solution is to not pass a second argument (app name) to any of the Firebase initializations. Looks like it's not needed in case you're referencing the same project.

Firebase functions with AdminSdk and RealtimeDatabase not working

I'd like to create, edit, read and delete on the RealTime Database using the firebase functions. Looking at other similar questions I saw that the AdminSdk has to be used, and so I did.
I basically copy/pasted the code provided by the same firebase guides.
const admin = require("firebase-admin");
const functions = require("firebase-functions");
admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: "https://<DATABASE_NAME>.firebaseio.com"
});
const db = admin.database();
db.ref("devices")
.once("value")
.then(snapshot => console.log("Snapshot: ",snapshot.val())
.catch(error => console.log(error))
});
In the initialization I set the credential with applicationDefault() as I previously set the GOOGLE_APPLICATION_CREDENTIALS env variable with my service_account_key.json path.
I tried anyway to set it with the cert method and the result didn't change. As 3 accounts are showed in the Service account section I tried with all of them as well.
This said,when starting the functions from console with 'firebase serve' the log is not showed and no error either.
Is there anything I'm missing? Some further configuration or whatever error you might be aware of?
Thank you in advance!
Update following your comments:
You want to "create, edit, read and delete on the Realtime Database using Cloud Functions", as indicated in your question, mimicking the behaviour of a Client SDK but from a server that you control. You should use one or more Cloud Functions that you call directly from this server. The most appropriate (based on your comments) would be to use an HTTPS Cloud Function.
For example you could have an HTTPS Cloud Function like the simple one below, to write to a specific node of the Realtime Database, as follows:
exports.writeToNode = functions.https.onRequest((req, res) => {
cors(req, res, () => {
const dbNode = req.body.nodeRef;
const objToWrite = req.body.nodeValue;
return admin.database().ref(dbNode).push(objToWrite)
.then(() => {
return res.send("Node " + dbNode + " updated!");
})
.catch(err => {
//please watch the official video https://www.youtube.com/watch?v=7IkUgCLr5oA&t=1s&list=PLl-K7zZEsYLkPZHe41m4jfAxUi0JjLgSM&index=3
});
});
});
You would call it by issuing a POST to the following URL https://us-central1-YOURPROJECTID.cloudfunctions.net/writeToNode, with a body like:
{
nodeRef: 'theNode',
nodeValue: {
firstName: 'John',
lastName: 'Doe'
}
}
Initializing the Admin SDK:
If you want to interact, from a Cloud Function, with the Realtime Database that is in the same Firebase project, you just need to initialize the Admin SDK without any parameter (i.e. admin.initializeApp();)
This way, the Admin SDK will use the Project's default service account, and will have full access to the Realtime Database (i.e. bypassing all the security rules).
So, initialize as follows:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
///// Additional thought /////
Note that you could maybe use the REST API exposed by the Realtime Database, instead of developing an entire set of CRUD endpoints through Cloud Functions. See https://firebase.google.com/docs/database/rest/start
REMAINING PART OF THE CONTENT OF THE INITIAL ANSWER, about background triggered Cloud Functions
You then need to declare a Cloud Function, as shown in the example below, by:
Selecting an "event handler";
Specifying the database path where it will listen for events and;
Executing the desired logic (normally using the data that was written at the path, or indicating that the node was deleted, etc...)
exports.makeUppercase = functions.database.ref('/devices/{pushId}/original')
.onCreate((snapshot, context) => {
// Grab the current value of what was written to the Realtime Database.
const original = snapshot.val();
console.log('Uppercasing', context.params.pushId, original);
const uppercase = original.toUpperCase();
// You must return a Promise when performing asynchronous tasks inside a Functions such as
// writing to the Firebase Realtime Database.
// Setting an "uppercase" sibling in the Realtime Database returns a Promise.
return snapshot.ref.parent.child('uppercase').set(uppercase);
});
This code snippet, copied from the documentation, will listen to any new node created under the devices node and will create an uppercase node the value of the original node in uppercase.
Note that this is a background triggered Cloud Function which is triggered when something "happens" at the specific path.
If you want to "create, edit, read and delete on the RealTime Database", as indicated in your question, mimicking the behaviour of a Client SDK, you may define one or more Cloud Functions that you call directly from your App. See the Callable Cloud Functions documentation.
You may alse read the following documentation items https://firebase.google.com/docs/functions/get-started and https://firebase.google.com/docs/functions/database-events and also watch the video series: https://firebase.google.com/docs/functions/video-series

How to put a trigger to another Firebase Database from Firebase Functions?

I'm trying to trigger my database function when something is wrote to a database from an associate.
I know that I need a Service Account created in the other database and the .json file that firebase gives to do the connection properly. Right now I'm giving all the permissions for getting sure than my errors don't come from this.
With what I found on the documentation and with other information on internet, this is how I login to the other database:
var adminAbi = require("firebase-admin");
var functionsAbi = require('firebase-functions');
const serviceAccount = require(`./serviceacountfile.json`);
adminAbi.initializeApp({
credential: adminAbi.credential.cert(serviceAccount),
databaseURL: 'https://DATABASEURL.firebaseio.com/',
},'test' );
And this is my trigger:
exports.copyDatabasess = functionsAbi.database.instance('test').ref('/messages/{user_id}/{now}').onWrite(event =>{
if (!event.data.exists()) {
return;
}
console.log('copydatabase', event.params.body);
// Grab the current value of what was written to the Realtime Database.
const original = event.data.val();
});
With this code I'm getting right now this error when I deploy:
! functions[copyDatabasess]: Deployment error. Failed to configure
Firebase Realtime Database trigger: unknown error, HTTP code 401
I really can't find useful informations about this error and how to solve it. If someone knows something about this would be much appreciated.
Thanks in advice.
You can't put a trigger on a database that's not in the same project as your functions. instance() only works with database shards in the same project.

How to access multiple Realtime Database instances in Cloud Functions for Firebase

I'm using multiple databases in a Firebase project. Cloud functions for the main (default) database work great, however, I cannot make them work for a secondary database. For example I want to make a read request on a node with admin privileges:
//this works
admin.database().ref(nodePath).once('value')...
This works in the main database, however, if I want to execute the command on another database, it doesn't work:
//this doesn't work
admin.database(secondaryDatabaseUrl).ref(nodePath).once('value')...
Although the functions are deployed, I get an error on the console when trying to execute the cloud function.
Here's the code for the cloud function with an https trigger:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const secureCompare = require('secure-compare');
exports.testFunction= functions.https.onRequest((req, res) => {
const key = req.query.key;
// Exit if the keys don't match
if (!secureCompare(key, functions.config().cron.key)) {
console.error('keys do not match');
res.status(403).send('error1');
return;
}
//test read request
//the line below crashes the function
return admin.database('https://secondary_db_url.firebaseio.com').ref(`/testNode`).once('value').then(dataSnapshot=> {
console.log('value', dataSnapshot.val());
return;
}).catch(er => {
console.error('error', er);
res.status(403).send('error2');
});
});
Below is the error log in the Firebase console:
TypeError: ns.ensureApp(...).database is not a function
at FirebaseNamespace.fn (/user_code/node_modules/firebase-admin/lib/firebase-namespace.js:251:42)
at exports.testFunction.functions.https.onRequest (/user_code/index.js:16:16)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/providers/https.js:26:41)
at /var/tmp/worker/worker.js:671:7
at /var/tmp/worker/worker.js:655:9
at _combinedTickCallback (internal/process/next_tick.js:73:7)
at process._tickDomainCallback (internal/process/next_tick.js:128:9)
If I don't specify the secondary database URL, the function will make the read request on my main database which works great:
//this works
return admin.database().ref(`/testNode`).once('value').then(dataSnapshot=> {
...
I'm using the latest SDK versions: "firebase-admin": "^5.5.1" and "firebase-functions": "^0.7.3"
So, how do I get an instance of a secondary database in cloud functions using admin privileges?
Here's how to access database by URL using Admin SDK:
let app = admin.app();
let ref = app.database('https://secondary_db_url.firebaseio.com').ref();
Here's an example from Admin SDK integration tests: https://github.com/firebase/firebase-admin-node/blob/master/test/integration/database.js#L52
With cloud functions > 1.1 now, here is the documentation link that saved my life on this issue.
https://firebase.google.com/docs/database/usage/sharding#connect_your_app_to_multiple_database_instances
So, it looks like this at the top of my my cloud function index.js :
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const dev = admin.initializeApp({
databaseURL: "https://appdev.firebaseio.com"
}, 'dev');
const v2 = admin.initializeApp({
databaseURL: "https://appv2.firebaseio.com"
}, 'v2');
and then, in my clond functions functions code I can do :
//will change stuff on default database
admin.database().ref().child(`stuff/${stuffId}`).set(myStuff)
//will change stuff on my dev database
admin.database(dev).ref().child(`stuff/${stuffId}`).set(myStuff)
//will change stuff on my v2 database
admin.database(v2).ref().child(`stuff/${stuffId}`).set(myStuff)
So it looks like you are trying to access multiple databases using the javascript web client API. Passing the URL of the database to the API like this doesn't work with the Admin SDK:
admin.database('https://secondary_db_url.firebaseio.com').ref(`/testNode`)
Instead, you have to initialize a second app, give it a name, and pass that app around to the Admin SDK APIs. Here's a complete sample that writes the same data to two different database instances in the same project:
const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)
const otherConfig = Object.assign({}, functions.config().firebase)
otherConfig.databaseURL = 'https://your-other-db.firebaseio.com/'
const otherApp = admin.initializeApp(otherConfig, 'otherAppName')
exports.foo = functions.https.onRequest((req, res) => {
const data = { foo: 'bar' }
const p1 = admin.database().ref('data').set(data)
const p2 = admin.database(otherApp).ref('data').set(data)
Promise.all([p1, p2]).then(() => {
res.send("OK")
})
.catch(error => {
res.status(500).send(error)
})
})
Updating this while on Firebase Functions v3.14.0. None of this answers worked for me so I implemented this solution
instance Registers a function that triggers on events from a specific Firebase Realtime Database instance
functions.database.instance('my-app-db-2').ref('/foo/bar')
Use the name of the database instance and it works, no need for the url. functions.database.ref used without instance watches the default instance for events.
So if both the answers doesn't work.
What happened with me is both the method worked without any error but second instance of database was not getting updated.
I updated npm and firebase CLI it worked.
Also #Dough Stevenson you Passing the URL of the database to the API like this **does** work with the Admin SDK
And this is a good blog from Firebase about the same
Firebase Blog : Easier scaling with multi-database support!

Cant get Firebase in Node JS app to run

I am trying to setup firebase JS client with NodeJS. So far here is my code
var firebase = require('firebase/app');
require('firebase/database');
var config = {
apiKey: "MY_SECRET_KEY_fhcWICPI",
authDomain: "my_fir_app.firebaseapp.com",
databaseURL: "https://my_fir_app.firebaseio.com",
};
var firApp = firebase.initializeApp(config);
firebase.database.enableLogging(true)
// Get a reference to the database service
var database = firebase.database();
Then here is one of my Firebase functions to save data to the real time database.
/**
* This will save the authors of stories in Firebase
* #param {String} id The ID of the author
* #param {String} firstName The authors first name
* #param {String} lastName The authors last name
* #param {Timestamp} dateCreated The unix time stamp when the author was created
*/
function saveStoryAuthor(id, firstName, lastName, dateCreated) {
database.ref('mystoriesdb/authors/' + id).set({
first_name: firstName,
last_name: lastName,
date_created : dateCreated
});
}
Finally, somewhere in the middle of my code am calling this function as
...
saveStoryAuthor('MZ8XWXNrkG', 'Dennis', 'Richie')
...
However, this is what I get in the logs (since I have enabled logging)
$ node index.js
p:0: Browser went online.
p:0: Making a connection attempt
getToken() completed. Creating connection.
c:0:0: Connection created
p:0: Failed to get token: Error: No transports available
p:0: data client disconnected
p:0: Trying to reconnect in 326.9669258513522ms
0: onDisconnectEvents
p:0: Making a connection attempt
getToken() completed. Creating connection.
c:0:1: Connection created
p:0: Failed to get token: Error: No transports available
I am probably doing something wrong. Could someone help.
It seems you havent created a service account in order to add firebase to your project with node js
Check out the documentation here.
var admin = require("firebase-admin");
var serviceAccount = require("path/to/serviceAccountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://<DATABASE_NAME>.firebaseio.com"
});
You need to change
var firebase = require('firebase/app');
to
var firebase = require('firebase');
Solution: Add the following to the root of your Webpack configuration:
resolve: {
mainFields: ['main']
}
Explanation:
The firebase/database package defines different entry points: main, browser, and module. NodeJS uses main. Webpack is a hipster and uses module.
The Firebase guys made some NodeJS-specific configuration (such as setting up WebSocket as a transport layer) in the entry point defined by main. So, after bundling, that special NodeJS-specific code isn't set up, so it errors out. Telling Webpack to just use main like Node resolves this issue.

Categories

Resources