Firebase is a platform for running and managing apps on the cloud. It’s a fully-featured “backend as a service” (BaaS) that scales automatically to millions of users. It offers tons of features for development (authentication, database, storage, messaging), user engagement (insights, experimentation, customization), and operations (testing, troubleshooting of stability and performance, feature rollout, and adoption monitoring). You just focus on writing your app and let Firebase manage the infrastructure and most of the tedious operational concerns.
Firebase is part of Google Cloud.
Make sure to get familiar with:
As of October, 2024, Firebase provides these 21 products:
Deprecated
There are additional sections in the documentation not counted as main products, but are essential to know about:
The documentation also shows how to link other Google products into your Firebase app, such as Google AdMob and Google Ads.
When developing and managing your project, you use either or both of:
In your app itself, you access Firebase services through either or both of:
The general idea is to first create a project (generally you will do this on the Firebase Console), and add apps and services (and only the services you need).
There is complete documentation for Firebase setup on:
And if you are using Firebase form a server, check the docs for how to get set up with the Admin SDK.
Before we get to our code along, a word about security!
Firebase is part of the Google Cloud, so if you are connecting to Firebase services from a server, or the console, your authentication and authorization will be configured through Google Cloud’s IAM.
But what about that SDK for the web client?, How can a web client talk to...a database 😬? The answer is: security rules. You configure rules that will examine all requests from a client—the same kinds of rules you would put in a server that your write yourself. (These security rules apply only to client SDKs; server, console, and API access use IAM instead).
It’s a good idea to learn everything you can about security rules, both in terms on the general approach and the specifics for different products:
So, yup, if a web client is talking directly for Firebase, there is no way to hide your database credentials at all, and you must rely on the security rules to prevent all the bad things. You can “do a little more” beyond the security rules, for example, you can:
There are some articles with security tips floating around the web, including How to Keep Your Firebase Project Safe and Secure from everyone.
If you have a Google account and are logged in, you can go directly to your Firebase Console and start exploring. Maybe look at the overview page of the docs and follow links to various guides, code labs and tutorials, quick starts, and samples. The Fundamentals page is a good one.
After browsing the guides, reading about the fundamentals, doing a quick start or code lab, and maybe watching some introductory videos, you will be ready to create a Firebase project of your own from scratch.
Two PlaylistsThe Net Ninja has a playlist on developing web apps with Firebase, using Vanilla JS. For a playlist that uses React, but fewer Firebase features, try this one from Logicism.
It is possible that they are a little out of date, as things change so fast.
Or want to learn from these notes? Let’s do a code-along!
Let's build a blog web application using React with Firebase Authentication, Firestore, and Hosting.
There are things to do before we really begin.
useState
and useEffect
.npm install -g firebase-tools
. (You may have to run this with sudo
or as an administrator (Windows). You might have to mess with OS settings to allow writing to certain system files to make it work. Details differ between systems, but you can do it!). Verify the tools are installed with the command firebase --version
.We don’t need to start completely from scratch. I’ve provided starter code with very, very basic blog functionality using React only (no Firebase). Our code-along is about Firebase, after all, since we’ve already seen Vite and React.
npm install
and npm run dev
in the VS Code terminal, as you would for any Vite application.. ├── README.md ├── eslint.config.js ├── vite.config.js ├── index.html ├── package-lock.json ├── package.json └── src ├── main.jsx ├── components │ ├── App.css │ ├── App.jsx │ ├── Article.jsx │ ├── ArticleEntry.jsx │ └── Nav.jsx └── services └── articleService.js
Firebase time!
</>
), then on the add page, follow the instructions to:
npm install firebase
on the command line in your project folder.import { initializeApp } from "firebase/app" import { getAuth } from "firebase/auth" import { getFirestore } from "firebase/firestore" const firebaseConfig = { // THE CONFIG FROM YOUR FIREBASE CONSOLE YOU BROUGHT IN EARLIER } export const app = initializeApp(firebaseConfig) export const auth = getAuth(app) export const db = getFirestore(app)
Let’s wire in the authentication. Even though this is probably your first run through this topic, we are not going to take any shortcuts, but rather do things in an organized, disciplined manner, with, um, lots of little files. It will look like more work than necessary, but this code-along strives to teach proper separation of concerns. Our goal is a layered, modular architecture. Here’s the overview:
The big idea: Components never talk to Firebase directly. Only the services do. You will notice that the services hide the complexities of Firebase from the components.
login
and logout
, hiding the complexities of Firebase auth. It will also contain a custom hook to notify components when the authentication state changes. Here is the file:
import { useState, useEffect } from "react" import { signInWithPopup, GoogleAuthProvider, signOut } from "firebase/auth" import { auth } from "../firebaseConfig" export function login() { return signInWithPopup(auth, new GoogleAuthProvider()) } export function logout() { return signOut(auth) } export function loggedInUserDisplayName() { return auth.currentUser.displayName } export function useAuthentication() { const [user, setUser] = useState(null) useEffect(() => { return auth.onAuthStateChanged((user) => { user ? setUser(user) : setUser(null) }) }, []) return user }
Services are awesomeComponents will only call
login
andlogout
and don’t even need or care that anauth
object is needed, nor that authentication providers are needed. Heck, they don’t even know that Firebase is there! In principle, we can swap out Firebase with a different backend without changing our components.
import { login, logout, loggedInUserDisplayName } from "../services/authService" export function SignIn() { return <button onClick={login}>Sign In</button> } export function SignOut() { return ( <div> Hello, {loggedInUserDisplayName()} <button onClick={logout}>Sign Out</button> </div> ) }
import { useEffect, useState } from "react" import Nav from "./Nav" import Article from "./Article" import ArticleEntry from "./ArticleEntry" import { SignIn, SignOut } from "./Auth" import { useAuthentication } from "../services/authService" import { fetchArticles, createArticle } from "../services/articleService" import "./App.css" export default function App() { const [articles, setArticles] = useState([]) const [article, setArticle] = useState(null) const [writing, setWriting] = useState(false) const user = useAuthentication() // This is a trivial app, so just fetch all the articles only when // a user logs in. A real app would do pagination. Note that // "fetchArticles" is what gets the articles from the service and // then "setArticles" writes them into the React state. useEffect(() => { if (user) { fetchArticles().then(setArticles) } }, [user]) // Update the "database" *then* update the internal React state. These // two steps are definitely necessary. function addArticle({ title, body }) { createArticle({ title, body }).then((article) => { setArticle(article) setArticles([article, ...articles]) setWriting(false) }) } return ( <div className="App"> <header> Blog {user && <button onClick={() => setWriting(true)}>New Article</button>} {!user ? <SignIn /> : <SignOut />} </header> {!user ? "" : <Nav articles={articles} setArticle={setArticle} />} {!user ? ( "" ) : writing ? ( <ArticleEntry addArticle={addArticle} /> ) : ( <Article article={article} /> )} </div> ) }
Time to replace the mock blog data with data from a real database. In a previous step, you should have added the Cloud Firestore product, created a database, and initialized the security rules. If you did not do those things, don’t worry, you can do them now. After your database is created, continue with the following steps:
articles
. Each document should have fields title
(of type string), body
(of type string), and date
(of type timestamp). Let Firebase choose the ids.fetchArticles
and createArticle
:
Of course, we have an article service in the starter code, but it used mock articles. It’s time to use the database instead of the fake data. Replace the entire file src/services/articleService.js with the following code, which we’ll explain in class:
// This service completely hides the data store from the rest of the app. // No other part of the app knows how the data is stored. If anyone wants // to read or write data, they have to go through this service. import { db } from "../firebaseConfig" import { collection, query, getDocs, addDoc, orderBy, limit, Timestamp, } from "firebase/firestore" export async function createArticle({ title, body }) { const data = { title, body, date: Timestamp.now() } const docRef = await addDoc(collection(db, "articles"), data) return { id: docRef.id, ...data } } // NOT FINISHED: This only gets the first 20 articles. In a real app, // you would implement pagination. export async function fetchArticles() { const snapshot = await getDocs( query(collection(db, "articles"), orderBy("date", "desc"), limit(20)) ) return snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data(), })) }
Try it out. It won’t work. Check your console.
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow write: if request.auth.uid == '****************************'; allow read: if request.auth != null; } } }substituting your Google user id for the asterisk sequence. This way, only you can write to the Firestore but anyone can read as long as they are logged in. (You can find your uid on the Auth part of the console if you enabled Google sign-in and you added yourself as the user.)
Let’s get our work saved.
Now we want the world to run it.
$ npm run build
This will create a dist folder with all the files you need to deploy.
If you did not add hosting during your Firebase setup, you can do it now. Add hosting and follow the steps indicated in the console.
If you did not install the Firebase CLI tools, you can do that now, too.
$ firebase login
so that you are authenticated for all subsequent commands.
$ firebase init
When asked to select features, just choose hosting, since auth and firestore were already selected in the console. For the other questions: Select Use an existing project, namely your existing project, and choose dist when asked about your project’s public directory.
$ firebase deploy
https://{YOURPROJECT}.web.app/__/firebase/init.js
, you will see your credentials, just like the whole world can. Now does this mean people can create an app of their own and hit your Firestore and mess up your database? Yes, but there’s one thing we can do.localhost
.Firebase: Error (auth/unauthorized domain)
in the browser console. But your deployed app does work. If you deploy this app anywhere except on your authorized domains, no one can log in. And since your security rules are set up so that only authenticated users can read, the only way people can read the blog is to be logged in on your deployed app. No one else can deploy (unless they take over your Google account...but you won’t let that happen, right?)Keep working on the app to make it nice. There is so much we can improve.
A few things come to mind:
When ready to deploy changes, you just need to do firebase deploy
(since you have already logged in and initialized the project).
What else?
You will want to test on localhostWhen doing development, you will likely be testing on localhost so that you don’t have deploy after every single change.
While testing this way, you’ll jump back into your Authorized Domains section and re-add
localhost
.
This was a super basic introduction.
There are many more things to learn about Firebase. We used Firebase Authentication and the Cloud Firestore database. But we did not learn about them deeply. It’s worthwhile visiting the documentation and studying these products in depth.
After tackling Authentication and Cloud Firestore, consider learning about:
Here are some questions useful for your spaced repetition learning. Many of the answers are not found on this page. Some will have popped up in lecture. Others will require you to do your own research.
import { getFirestore } from 'firebase/firestore'
npm install -g firebase-tools
npm install firebase
and store the configuration data in a JavaScript object.auth
object are you most likely to determine whether a user has logged in or out? onAuthStateChanged
signInWithPopup
on the auth
object.allow write: if request.auth.uid == '123';
firebase login
, firebase init
, and firebase deploy
We’ve covered: