[MUSIC PLAYING]
TODD KERPELMAN: Hello, everybody.
And welcome to this Firebase Fundamentals video,
where I'm going to show you how to get up
and running quickly using Firestore in your web
application.
Now this video assumes that you have already
gotten started with the initial Firebase setup using something
like webpack or Rollup, as David has described
in his Getting Started video.
If you don't know what I'm talking about,
I recommend you check out his video first, and then come back
to this one before continuing.
And if you are looking for a more detailed explanation
around some of the things I'm doing,
how Firestore really works underneath the hood,
or the difference between a document and a collection,
I would recommend that you check out the Getting Started
Firecast that will be a little more detailed
and should be available sometime after I make this video.
And when it is, we will link to it in the description below.
All right, I guess that's enough preamble for now.
Let's get coding.
OK, so I'm going to go ahead and start
with a nearly empty project, kind of like what David
had at the end of his video.
So I have my code here in index.js,
which then gets compiled to my main.js file, which
I am reading in here.
Now for testing purposes, I'm going
to go ahead and call npx webpack dash dash
watch to automatically compile everything
that I'm changing in here.
And then I will serve up what's in my dist directory
so that I can test it.
I'm going to do that by using the npm serve command here,
but you could also do this using Firebase serve as well.
Either way is good.
So let me make a quick change to my code.
I'll refresh my page here.
And yes, it looks like that change got captured and served
over, so we are good to go.
OK, so to get started using Firestore in your project,
you'll first want to go to the Firebase Console,
head on over to the Firestore tab,
and click this Create Database button.
You're going to select your initial set of security rules
and your database location.
For me, that's us-west.
And then once that's done, you will go back
to your web project.
Now if you've already installed the latest version of Firebase
using NPM, you should already have
all the libraries you need.
So at the top of your code, you'll
just want to start importing from the firebase/firestore
library.
Now if you're not going to be taking advantage
of any of the real-time listeners, offline support,
or some of the latency compensation
that our client SDKs provide, then you
could also use the firebase/firestore/lite
library.
Now this library only lets you perform simple CRUD operations
but is significantly smaller in size.
For now though, I'm going to stick to the full library.
Then to access Firestore, just call getFirestore in your code
sometime after you've called initializeApp.
You can also use initializeFirestore
if you need to initialize Firestore
with any non-default settings, kind of like this.
But I am going to use the simpler getFirestore for now.
And I should also note that as I'm doing this,
I'm going to need to import this function up above.
I'll be honest-- a few weeks ago,
my IDE was doing all this for me,
and I almost never had to worry about it.
But shortly after I updated it, it mysteriously
stopped automatically importing for me.
No idea why, but for now, I'm going
to be adding all these imports manually.
And you'll probably need to make sure either
that you are doing the same or that your IDE is importing
these individual functions for you.
Now to write to a document, you're
first going to start with a document reference.
And to create one of these, you will call the doc function,
pass in your reference to Firestore,
and then the path to your document.
Now paths in Firestore always take
the form of collection, document, collection, document,
and so on, which means document paths should always
have an even number of elements-- first,
the collection, then the document, then
the subcollection, and so on.
So in this case, I'm going to be writing
to 2021-09-14 document in the daily special collection.
Now, this document here is using an absolute path,
but you can also use relative paths
by calling doc, passing in your initial document reference,
and then a path to a child off of that document reference,
like so.
OK, to write to a document, you're
going to use the setDoc command.
You'll pass in the reference to the document, which we created
up above, as well as the document data
that you want to write, which I will just add up above here.
Generally speaking, you're going to represent your document
as an object consisting of key-value pairs,
kind of like this.
So let me just call this function really quick.
I'm going to run my code.
And I can see my document there in the Firebase Console.
Now the setDoc command will write the document
if it doesn't exist, and it will completely
replace any document that already
exists at this location.
Now if that's not the behavior you want,
the updateDoc function will only overwrite the fields
that you specify while keeping all your old data in place.
However, this updateDoc call will throw an error
if the document doesn't already exist.
And if that's not the behavior you want,
you can use the setDoc command with merge true
passed in as your third argument.
This will create the document if it
doesn't exist and only replace the fields that you've
specified if it does.
Which call you use really depends on what behavior
you want from your app.
Now writing to a document is an asynchronous call
that returns a promise.
So you could put this in an async await call,
with appropriate error checking, of course,
or you can use a .then callback.
Just keep in mind, in both situations,
that this call will only continue after the write has
been confirmed by the server, even though it will be
written locally to your cache.
So be careful here.
Don't block the UI or anything while you're
waiting for this call to complete,
because if your user is offline while they're using your app,
it will look like your application is frozen.
And to be clear, most of the time
you won't need to wait for this write
to be confirmed before you start using it.
Because of some fancy latency compensation
that our SDK already does, you'll
be able to access this new data right away without actually
having to wait for it to be confirmed by the server.
OK, very often, you'll want to add a new document
to a collection without having to specify the document
ID like I did above.
In those kind of situations, you can use the addDoc call.
This will create a new document in your collection
with a randomly generated ID.
Note that when you do this, you're
going to be calling addDoc with a collection reference instead
of a document reference.
But other than that, the call looks pretty similar to setDoc.
I guess the other difference is that this call will return
a document reference to the document that has been created,
in case you want to know the ID or the path of the document
that you just added.
To read in a single document, use the getDoc command,
passing in the document reference for the document
that you want to read.
Now this returns a promise, which
resolves to a document snapshot, which
is basically an object that represents your document.
With this snapshot, you can get some metadata
about the document, make sure the underlying document really
exists, and most importantly, grab the data that it
contains by calling data on it.
So that's what I'm going to do in this example.
I'll see if my document exists, and if it does,
I will extract my data, like so, and print out
a stringified version of it to the Console.
So just note that "exists" and "data" here are both methods,
not properties.
That's messed me up more than I care to admit.
And while this is nice, one of the really great things
about Firestore is that it can also let you know in real time
when your document has changed.
So let's look at how to do that with a snapshot listener.
To read a document in real time, you will use onSnapshot call.
You'll pass in the reference of the document that you want
to listen to, as well as the listener itself, which
is a function that takes in a snapshot reference
and does something interesting with the data.
In my case, I'll once again check
to see if the document exists and then print it out
to the console.
Note that this listener will fire the very first time you
set up your snapshot listener, as you can see here, and then
also fire any time thereafter when your document changes.
Like if I were to change the price or the description
of my daily special, like this, you
can see that the listener gets called again.
Since leaving a listener open does
involve network usage for your users
and database costs for you, I do recommend
disabling your listeners when you no longer need
the data from this document.
Now usually, that's going to be during unloading
type of moments in your frameworks, router event,
or component's lifecycle.
Although if you are using Firebase's own ReactFire
or AngularFire libraries, they already
handle a lot of this for you.
But to do this yourself, the onSnapshot listener
automatically returns a function that you
can call to unsubscribe from that listener.
Just keep that return value in a variable somewhere,
and then call it at the appropriate time.
So being able to read in a single document is useful,
but often, you'll want to grab multiple documents at once.
And you'll do this by creating a query.
And to create a query, you will call query,
and then pass in a list of constraints for that query.
Now the most common queries are ones
where you are searching for a certain set of documents
in a single collection.
So you will generally pass in a collection reference
as the first constraint, meaning that you're
going to be querying against that particular collection.
Now whatever else you pass in is up to you.
Often, there will be a where clause to grab,
for instance, all orders where the drink type equals latte.
You could add an orderBy clause as well,
where we could sort by the order time or the customer name.
Note, however, that this specific case
of filtering on one field and then sorting by another
will require my setting up a custom index, which Firestore
will helpfully point out to me in the console
through this error message.
I can just click on that link, and I can create the index.
I can also limit my query to, say, the first 10
or the last 20 results that come in.
And I do recommend that you set some sort of limit,
to make sure you're not loading in more data
than your customer would reasonably want,
particularly as your database grows
and your collections get very large.
This will save them data usage, and it will save you money.
Now once you've built your query,
you can retrieve your documents by making
a getDocs call-- that's Docs with an s--
and passing in your newly created query.
This function is very similar to the getDoc call described
earlier, except you'll be getting back a query snapshot
instead of a document snapshot.
Now a query snapshot will contain
an array of documents that were returned
by the query, which you can get at by calling the docs method.
Or if you want to iterate over them one at a time,
we let you call for each directly on the query snapshot.
Either way, you can then get at your document data
by calling the data method on the individual documents
that you've retrieved, like so.
And-- whoops, it looks like I'm getting nothing here.
Oh, that's because these searches are case sensitive,
and Latte should have a capital L. All right, that's better.
And by the way, this is probably a good time to remind you that
all this time, through the magic of editing,
I have been adding all these functions-- getDocs, query,
even query constraints like limit and orderBy--
to my imports as I need them.
Now this all plays into the nice tree
shakeableness of the new Firebase SDK,
which leaves your users with less code
to download once it's been bundled up.
And as we add more features over time,
they'll stay out of your app until you decide you explicitly
want to use them.
But if I were to forget to include one of these,
or my IDE weren't auto-including them properly,
I'd be getting errors like this, where
it's saying things like, "limit is not defined,"
and that's why.
Oh, also, I'm just going to remove this bit here,
since we no longer need it.
And query support real-time updates as well.
Basically, all you need to do here
is call onSnapshot and pass in your query instead
of a document reference.
And then in your listener callback,
you'll once again receive a query snapshot,
and you can process it the same way you would
as if you had called getDocs.
Here, I guess let me do it a little more
functional programmer-y with a map function.
All right, there you go.
And as before, this returns a function
that you can call to cancel this listener, so save it
in a variable so you can call it later.
One last thing note here is that if one of these documents
changes, Firestore, the database,
will send that newer version of the document over to my client.
And then my client will combine that new document
with the cached versions it already has
and send my listener callback the entire list
of 20 documents again.
And that's usually what you want.
It means that if I were, say, converting
this list of documents into a group of list elements
that I could display on screen, I
can just rebuild this group every time
and not worry about any conditional logic
here around like, oh, was this document changed or added
or so on.
But you can always get that specific change data
if you need it.
So those are the basics or the fundamentals
of Firestore on the web.
But let me leave you with a few quick notes before I go.
First, as I mentioned earlier if I
were to use the firebase/firestore/lite
library, that would reduce my imported code
size between like 60% to 80%.
But I won't have access to things
like offline support, latency compensation,
or those fancy real-time listeners.
In fact, you can see here, it's telling me
that onSnapshot call is no longer defined, which is sad.
So I'm going to go back.
Second, as I mentioned earlier, my security rules
are currently set up so that anybody out there
can delete my entire database.
That's definitely something I should fix
and a factor I should be considering now while I'm still
designing my database instead of, say,
two days before I launch.
And third, if you're worried about messing with production
data or just want to save a little money as you're
developing, try running against the emulators.
This involves installing the emulator from the Firebase CLI
tool, which you can do by typing firebase init or, I think,
firebase init emulator, starting it up by calling firebase
emulators:start, and then pointing your code
to the emulator, like so.
And finally, if you have more questions around how
to structure your data, how security rules work,
how pricing works, or just about anything else,
I recommend checking out the get to Cloud Firestore series.
It's pretty good, but you know, I am a little biased there.
So those are the fundamentals for adding Firestore
to your web application.
Hey, if you like this video, why not
subscribe to our YouTube channel?
You'll get to be the first on your block
to know when we've added a new video.
And if you want to see more videos like this,
make sure to check out the Firebase Fundamentals playlist.
We'll be adding more videos to that as time goes on.
So thank you very much for watching.
I will see you soon, and happy coding.
[MUSIC PLAYING]