I'd like to perform a query on my database once a cloud function on my Firebase app is called.
Let's say I have a certain trigger on the database, consider the example provided in the get started guide on Firebase.
// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
.onWrite(event => {
// Grab the current value of what was written to the Realtime Database.
const original = event.data.val();
console.log('Uppercasing', event.params.pushId, original);
const uppercase = original.toUpperCase();
// I'D LIKE TO PERFORM A QUERY HERE, JUST A SIMPLE RETRIEVE BASED ON THE ID PROVIDED
// 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 event.data.ref.parent.child('uppercase').set(uppercase);
});
Which modules should I import, if any?
How can I perform the query on the DB?
Thank you in advance for your answer!
You can use the Node.js Admin SDK for this:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.makeUppercase = functions.database()
.ref('/messages/{pushId}/original')
.onWrite(event => {
return admin.database().ref('/other')
.orderByChild('id').equalTo(event.params.pushId)
.once('value').then(snapshot => {
// there, I queried!
});
});
Related
I am trying to deploy Firebase Functions from xcode using this tutorial - https://firebase.google.com/docs/functions/get-started. This is my index.js -
'use-strict'
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access Firestore.
const admin = require('firebase-admin');
admin.initializeApp();
// Take the text parameter passed to this HTTP endpoint and insert it into
// Firestore under the path /messages/:documentId/original
exports.addMessage = functions.https.onRequest(async (req, res) => {
// Grab the text parameter.
const original = req.query.text;
// Push the new message into Firestore using the Firebase Admin SDK.
const writeResult = await admin.firestore().collection('messages').add({original: original});
// Send back a message that we've successfully written the message
res.json({result: `Message with ID: ${writeResult.id} added.`});
});
// Listens for new messages added to /messages/:documentId/original and creates an
// uppercase version of the message to /messages/:documentId/uppercase
exports.makeUppercase = functions.firestore.document('/messages/{documentId}')
.onCreate((snap, context) => {
// Grab the current value of what was written to Firestore.
const original = snap.data().original;
// Access the parameter `{documentId}` with `context.params`
functions.logger.log('Uppercasing', context.params.documentId, original);
const uppercase = original.toUpperCase();
// You must return a Promise when performing asynchronous tasks inside a Functions such as
// writing to Firestore.
// Setting an 'uppercase' field in Firestore document returns a Promise.
return snap.ref.set({uppercase}, {merge: true});
});
After I run either of the commands "firebase deploy --only functions" or "firebase deploy --only functions:addMessage", it shows message -
"✔ Deploy complete!
I don't see the functions deployed in the firebase console and it shows message - Project Console: https://console.firebase.google.com/project/testpro-92351/overview
Apples-MacBook-Air:Fireupgoodsa apple$".
but I expect to see "Function URL (addMessage): https://us-central1-MY_PROJECT.cloudfunctions.net/addMessage".
Note that:-
My app name is "Fireupgoodsa" under the project "testpro-92351".
I have activated the blaze plan.
Firestore is working fine.
What am I missing out on ?
I have also other question, slightly related - I am trying to link Firestore to Stripe-Payments Is it necessary to deploy functions to Firebase ?
Thanks,
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
I have a Firebase cloud function. Everything works as expected within the helloWorld function except the line deedRef.limitToLast(1).remove(); I also tried to do .ref(/deeds/${deedID}).remove() is there a reason why I can't remove data from firebase within cloud functions? The output from the http request is "Error: could not handle the request".
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const deedRef = admin.database().ref('/deeds');
const oldDeedRef = admin.database().ref('/oldDeeds');
exports.helloWorld = functions.https.onRequest((req, res) => {
deedRef.limitToLast(1).once("value", (snapshot) => {
snapshot.forEach((deedSnapshot) =>{
let deedID = deedSnapshot.val().id;
let text = deedSnapshot.val().message;
oldDeedRef.push({
id: deedID,
message: text
})
})
})
deedRef.limitToLast(1).remove();
res.send("Congrats For running the function");
});
The problem has nothing to do with Cloud Functions.
deedRef.limitToLast(1) returns a Query type object. Query doesn't have a method called remove(). Therefore, your code will fail at runtime with a message to that effect.
If you want to delete some data from Realtime Database, you're going to need a Reference type object, which has a remove() method. This will remove everything at the location of the reference.
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!
My firebase database structure looks like this:
-events
-uniqueEventId
-endTimeStamp: 1507949100
-active: true
-uniqueEventId2
-endTimeStamp: 1807949100
-active: true
-uniqueEventId3
-endTimeStamp: 1900949100
-active: true
How do I cloud function which can get all the events with timestamps before the current time and set their active to false.
I'm not sure how you want Cloud Functions to be triggered in this case. I'll assume you want a HTTP trigger, so that you can simply call it from the browser or a web hook.
That means you start with a basic HTTP-triggered function:
exports.updateStatus = functions.https.onRequest((req, res) => {
// ...
res.status(200).send("done");
});
Next up you'll need to access the Realtime Database within this function. To do that you'll use the Firebase Admin SDK, which gives you easy administrative access within your code:
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.updateStatus = functions.https.onRequest((req, res) => {
// ...
res.status(200).send("done");
});
Then we get to the actual code to change the data. This is standard database access code and has little to do with Cloud Functions. In this case you want to query for timestamp, loop over the results, and set the active property:
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.updateStatus = functions.https.onRequest((req, res) => {
let now = Date.now();
let query = admin.database().ref("events").orderByChild("timestamp").startAt(now);
query.once("value").then(function(snapshot) {
var promises = [];
snapshot.forEach(function(child) {
promises.push(child.ref.update({ active: false }));
})
Promise.all(promises).then(function() {
res.status(200).send("done");
});
});
});
That last code is a bit tricky, since it deals with many asynchronous write operations. Cloud Functions will terminate your function after your send the response to the client, so it's important that you only send a response back after all those asynchronous writes are done. I use a Promise.all() for that. For more information on this asynchronous nature, read the documentation, this blog post, and watch this video.
For more info, I'd recommend studying the documentation for the Firebase Admin SDK.