With Cloud Functions
, you can handle events in the Firebase Realtime Database
with no need to update client code. Cloud Functions
lets you run Realtime Database
operations with full administrative
privileges, and ensures that each change to Realtime Database
is processed
individually. You can make Firebase Realtime Database
changes via the DataSnapshot
or via the Admin SDK
.
In a typical lifecycle, a Firebase Realtime Database function does the following:
- Waits for changes to a particular Realtime 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 a data object that contains a snapshot of the data stored in the specified document.
Trigger a Realtime 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 Realtime Database
path where it will listen for events.
Set the event handler
Functions let you handle Realtime 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 Realtime Database . -
onCreate()
, which triggers when new data is created in Realtime Database . -
onUpdate()
, which triggers when data is updated in Realtime Database . -
onDelete()
, which triggers when data is deleted from Realtime Database .
Specify the instance and path
To control when and where your function should trigger, call ref(path)
to specify a path, and optionally specify a Realtime Database
instance
with instance('INSTANCE_NAME')
. If you do not
specify an instance, the function deploys to the default Realtime Database
instance for
the Firebase project For example:
- Default Realtime 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 Realtime 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 EventContext.params
object of your function. In this example, the value is available as context.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, the data object returned is a DataSnapshot
.
For onWrite
or onUpdate
events, the
first parameter is a Change
object that contains two snapshots
that represent the data state before
and after the triggering event. For onCreate
and onDelete
events,
the data object returned is a snapshot of the data created or deleted.
In this example, the function retrieves the snapshot for the specified path, converts the string at that location to uppercase, and writes that modified string to the database:
// 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' ) . onCreate (( snapshot , context ) = > { // Grab the current value of what was written to the Realtime Database . const original = snapshot . val (); functions . logger . 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 ); });
Accessing user authentication information
From EventContext.auth
and EventContext.authType
,
you can access
the user information, including permissions, for the user that triggered
a function. This can be useful for enforcing security rules,
allowing your function to complete different operations based on the user's
level of permissions:
const
functions
=
require
(
'firebase-functions/v1'
);
const
admin
=
require
(
'firebase-admin'
);
exports
.
simpleDbFunction
=
functions
.
database
.
ref
(
'/path'
)
.
onCreate
((
snap
,
context
)
=
>
{
if
(
context
.
authType
===
'ADMIN'
)
{
//
do
something
}
else
if
(
context
.
authType
===
'USER'
)
{
console
.
log
(
snap
.
val
(),
'written by'
,
context
.
auth
.
uid
);
}
});
Also, you can leverage user authentication information to "impersonate" a user and perform write operations on the user's behalf. Make sure to delete the app instance as shown below in order to prevent concurrency issues:
exports
.
impersonateMakeUpperCase
=
functions
.
database
.
ref
(
'/messages/{pushId}/original'
)
.
onCreate
((
snap
,
context
)
=
>
{
const
appOptions
=
JSON
.
parse
(
process
.
env
.
FIREBASE_CONFIG
);
appOptions
.
databaseAuthVariableOverride
=
context
.
auth
;
const
app
=
admin
.
initializeApp
(
appOptions
,
'app'
);
const
uppercase
=
snap
.
val
()
.
toUpperCase
();
const
ref
=
snap
.
ref
.
parent
.
child
(
'uppercase'
);
const
deleteApp
=
()
=
>
app
.
delete
()
.
catch
(()
=
>
null
);
return
app
.
database
()
.
ref
(
ref
)
.
set
(
uppercase
)
.
then
(
res
=
>
{
//
Deleting
the
app
is
necessary
for
preventing
concurrency
leaks
return
deleteApp
()
.
then
(()
=
>
res
);
})
.
catch
(
err
=
>
{
return
deleteApp
()
.
then
(()
=
>
Promise
.
reject
(
err
));
});
});
Reading the previous value
The Change
object has a before
property that lets you inspect what was saved to Realtime Database
before
the
event. The before
property returns a DataSnapshot
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 DataSnapshot
or reading the after
property. This property on any Change
is another DataSnapshot
representing
the state of the data after
the event happened.
For example, the before
property can be used to make sure the function only
uppercases text when it is first created:
exports
.
makeUppercase
=
functions
.
database
.
ref
(
'/messages/{pushId}/original'
)
.
onWrite
((
change
,
context
)
=
>
{
//
Only
edit
data
when
it
is
first
created
.
if
(
change
.
before
.
exists
())
{
return
null
;
}
//
Exit
when
the
data
is
deleted
.
if
(
!
change
.
after
.
exists
())
{
return
null
;
}
//
Grab
the
current
value
of
what
was
written
to
the
Realtime
Database
.
const
original
=
change
.
after
.
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
change
.
after
.
ref
.
parent
.
child
(
'uppercase'
)
.
set
(
uppercase
);
});