[MUSIC PLAYING]
ANDREA WU: Hi, everyone.
I'm Andrea from the Firebase team.
And today, we're going to look at how to get started
with Realtime Database on web.
Why would you want to use Realtime Database
in the first place?
Well, you can use it to store and sync data using a NoSQL
Cloud database.
Data is stored as JSON, synced across all clients
in near real time, and remains available when
your app goes offline.
The best part of all this is it can
be done with just a few lines of code,
and Firebase manages the infrastructure of the database
system so you don't have to spend time
worrying about building, maintaining,
and hosting your own database.
With that, let's go ahead and see how we would do this.
There are many ways to include a library in a web app.
But in this video, we'll be using NPM and webpack.
If you want more information on how to set that up
with Firebase, check out this other "Firebase
Fundamental" video.
Let's get started.
First off, let's go to Firebase Console
and navigate to Realtime Database.
Clicking on Create Database will open a window
where we'll pick a location for our Realtime Database.
The default is usually fine as it's
the project's default Google Cloud platform resource
location.
If you're not able to pick a location,
then your project already has a default GCP resource location
that was set either during project creation
or when setting up another service,
like Storage, that requires a location setting.
We can then click Next to pick our security rules.
The default security rules deny all reads and writes
from our apps, which probably wouldn't be very helpful right
now since we wouldn't be able to change the database at all.
So we can go ahead and choose to Start in test mode
so we can actually write data to our database.
And we can come back to update these security
rules when we're ready.
Then click Enable here, which will then
show our empty database.
What are we seeing here exactly?
Well, to understand what this is,
we'll have to talk a bit about how
Realtime Database stores data.
The tl;dr is that it's a JSON tree.
And when you add data to the JSON tree,
it becomes a node in the existing JSON structure
with an associated key.
You can provide your own keys, like user IDs
or semantic names, or Firebase can generate them for you.
What we see here in the Console is the root of the tree.
And when we add data to it, we'll
see the tree-like structure grow from here
as we'll see in a bit.
Even though this is a tree and Realtime Database
does allow nesting of data up to 32 levels deep, when you fetch
data at a location in the database,
you retrieve all of its children nodes.
So if you have a deeply nested tree,
this might slow down the user.
I also have to wait to get and parse through all of this data.
On top of that, granting read and write
access at a node grants access to all the data
under that node, which might not actually be what you want.
So in practice, keeping this data structure
as flat as possible will probably be helpful.
Now that we know how data is stored in the Realtime
Database, we're ready to take a look at some code.
Here, I'm in my editor and starting
from the same starter code you'll
have at the end of our setup video
linked in the description.
Following that video involves installing Firebase with NPM,
which means you'll have Realtime Database ready to use as well.
So now in our entry point file index.js,
we can initialize a Firebase app after grabbing
the configuration information from our Firebase project
in the Console.
Next, we'll have to get the instance
of Realtime Database, which is used to read and write data.
Every Firebase subpackage has a getter function
that retrieves an instance of a service,
like Realtime Database in this case.
As a side note, don't worry about calling
this getDatabase function multiple times,
as it returns the same instance when you call it
with the Firebase app.
Now let's see how we can write and read data
from Realtime Database.
We'll first have to create a reference
to a path, which is where in the database
we'll read and write data.
And we'll use the ref() function to do this.
The first argument is the database instance
that we already got from above, and the second argument
is the path at which we want to interact with the data.
For example, let's say we're creating a social exercise
tracking app, and for it, we'll probably have to add users.
We'll call the ref() with db, then pass in the path
'users/' + whatever the user's ID is.
To write to the database, we'll use set() to save data to that
reference.
And if there's already data existing at that path,
the former data will get replaced.
Using the same reference example above for adding users
to our social exercise tracking app,
we'll call set() with a reference to add a user
to the database.
Once we call the writeUserData() function,
we can see here in Firebase Console that there is now this
new entry.
If we call that function again with all
of the same parameters, except for a different email,
the original email address for the given user ID
would be replaced with the new email,
as we can see here in the Console, as well.
Now that we know how to write data, how do we get that data?
Let's say we want to get the total distance a user has run.
We'll first get the reference to the data,
then use onValue to observe events.
onValue will get triggered once when the listener is attached,
and again every time the data, including
children data, changes.
The listener receives a snapshot that
contains the data at the specified path in the database
at the time of the event.
And we can get the value in the snapshot with the val() method.
This onValue() event is helpful for syncing the entire
app's state, since it returns all of the data
at the reference.
Often in our apps, we need to use lists.
And a question that comes to mind
is, what happens if I have a five-item list,
and for whatever reason, two devices randomly both
decide to add a sixth item at the same time?
Well, we can use Firebase's push() function,
which generates a unique key every time a child is added
to the specified Firebase reference.
This way, we're not trying to add
an item at a particular index in the list, which
may result in an override if device A adds to index 6,
then device B does the same thing.
By using these autogenerated unique keys
for each new element in the list,
several clients can add children to the same location
at the same time without write conflicts.
Each key generated by push() is based on a timestamp,
so list items are automatically ordered chronologically.
For example, push() could be used to add a new post
to a list of comments to workouts in our app.
Now how do I get that list of comments?
Before, we had talked about how to get data
at a specific reference, but that data wasn't a list.
We can again use onValue().
But instead of using the val() function on the snapshot,
we'll iterate through the snapshot,
which returns the entire list of data.
For each item in the snapshot, we'll get the corresponding
value using the val() function.
Sometimes, though, we don't want to process the entire list.
And, in fact, we probably don't want
to do that most of the time because it's
getting a lot of data.
We likely care more about the changes happening in that list,
so let's listen to child events instead.
These are triggered in response to specific operations that
happen to the children of a node from an operation,
such as when a new child node is added to the path.
There are some events we can listen for, and each of these
together can be useful for listening
to changes to a specific node in a database.
For example, in our social exercise tracking app,
we might use these methods together
to monitor activity in the comments of a workout.
onChildAdded() retrieves a list of items or listens
to additions to a list of items.
And this event is triggered once for each existing child,
and then again every time a new child
is added to the specified path.
The listener is passed a snapshot
with the new child's data.
So in this case, we'll get any added comments for our post.
onChildChanged() listens for changes to the items
in the list, and this event is triggered any time a child node
is modified, including any modifications to descendants
of the child node.
The snapshot passed to the event listener
contains the updated data for the child.
In our example, this will get triggered whenever
a comment is modified, and we'll be
able to see the modified text.
onChildRemoved() listens for items being removed from
a list, and this event is triggered when an immediate
child is removed.
The snapshot passed to the event listener
contains data for our removed child.
So in our example, this will get called
when a comment gets deleted, and we'll
be able to see what was deleted.
To make all of this much simpler,
there are open-source libraries for popular web frameworks
to use Realtime Database more easily,
AngularFire, ReactFire, and VueFire.
So that's it.
We can use Realtime Database to store and sync data as JSON,
and this data is synced across all clients in real time
and remains available when your app goes offline.
The Realtime Database JavaScript SDK is open source on GitHub,
so check that out.
And there are more "Firebase Fundamentals" videos
if you enjoyed this one.
Happy coding.