With Cloud Functions, you can handle events in the Firebase Realtime Database with no need to update client code. Cloud Functions lets you run database operations with full administrative privileges, and ensures that each change to the database is processed individually. You can make Firebase Realtime Database changes via the DeltaSnapshot
or via the Admin SDK.
In a typical lifecycle, a Firebase Realtime Database function does the following:
- Waits for changes to a particular database location.
- Triggers when an event occurs and performs its tasks (see What can I do with Cloud Functions?for examples of use cases).
- Receives an event data object that contains two snapshots of the data stored at the specified path: one with the original data prior to the change, and one with the new data.
Trigger a database function
Create new functions for Realtime Database events with functions.database
. To control when the function triggers, specify one of the event handlers, and specify the database path where it will listen for events.
Set the event handler
Functions let you handle database events at two levels of specificity; you can listen for specifically for only creation, update, or deletion events, or you can listen for any change of any kind to a path. Cloud Functions supports these event handlers for Realtime Database:
onWrite()
, which triggers when data is created, updated, or deleted in the Realtime Database.onCreate()
, which triggers when new data is created in the Realtime Database.onUpdate()
, which triggers when data is updated in the Realtime Database.onDelete()
, which triggers when data is deleted from the Realtime Database.
Specify the database instance and path
To control when and where your function should trigger, call ref(path)
to specify a path, and optionally specify a database instance with instance('INSTANCE_NAME')
. If you do not specify an instance, the function deploys to the default database instance for the Firebase project For example:
- Default database instance:
functions.database.ref('/foo/bar')
- Instance named "my-app-db-2":
functions.database.instance('my-app-db-2').ref('/foo/bar')
These methods direct your function to handle writes at a certain path within the database instance. Path specifications match all writes that touch a path, including writes that happen anywhere below it. If you set the path for your function as /foo/bar
, it matches events at both of these locations:
/foo/bar
/foo/bar/baz/really/deep/path
In either case, Firebase interprets that the event occurs at /foo/bar
, and the event data includes the old and new data at /foo/bar
. If the event data might be large, consider using multiple functions at deeper paths instead of a single function near the root of your database. For the best performance, only request data at the deepest level possible.
You can specify a path component as a wildcard by surrounding it with curly brackets; ref('foo/{bar}')
matches any child of /foo
. The values of these wildcard path components are available within the event.params
object of your function. In this example, the value is available asevent.params.bar
.
Paths with wildcards can match multiple events from a single write. An insert of
{
"foo": {
"hello": "world",
"firebase": "functions"
}
}
matches the path "/foo/{bar}"
twice: once with "hello": "world"
and again with "firebase": "functions"
.
Handle event data
When handling a Realtime Database event, event.data
is a DeltaSnapshot
. In this example, the function retrieves event.data
for the specified path, converts the string at that location to uppercase, and writes that modified string to the location /messages/{pushId}/uppercased
:
// 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 ();
// 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 );
});
Reading the previous value
The DeltaSnapshot
has a previous
property that lets you inspect what was saved to the database before the event. The previous
property returns a new DeltaSnapshot
where all methods (for example, val()
and exists()
) refer to the previous value. You can read the new value again by either using the original DeltaSnapshot
or reading the current
property on any DeltaSnapshot
to read a value as it is after the event.
For example, the previous
property could be used to make the makeUppercase()
function an uppercase-only new value:
// Only edit data when it is first created.
if ( event . data . previous . exists ()) {
return null ;
}
// Exit when the data is deleted.
if (! event . data . exists ()) {
return null ;
}
// 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 ();
// 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 );
});
Monitoring changed values
Sometimes you don't need the old value; you just need to know whether data has changed. You can see if data changed at a path using the changed()
function on DeltaSnapshot
. This function only calls createThumbnail()
if a change to a user profile also changed the value of profilePicture
:
exports.thumbnailProfile = functions.database.ref('/profiles/{userID}')
.onWrite(event => {
var eventSnapshot = event.data;
var profilePictureSnapshot = eventSnapshot.child('profilePicture');
if (profilePictureSnapshot.changed()) {
return createThumbnail(profilePictureSnapshot.val())
.then(url => {
return eventSnapshot.ref.update({ profileThumbnail: url });
});
}
});