Skip to content

About

A beginner-friendly React app that teaches Firebase fundamentals. Students build a Cat Journal using Firestore for data and Firebase Authentication for Google login.

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

Cat Journal — Intro to Firebase

A small React app for learning Firebase. You sign in with your Google account, write journal entries about your cat, and watch them appear in a list that updates in real time.

Everything is built except four pieces. Those four are marked TODO 1 through TODO 4 in the code, each with a comment explaining exactly what to write and what it teaches. Fill them in and the app works.

Stack: React + Vite, Firebase (Firestore + Authentication). Nothing else.


Prerequisites

  • Node.js 18 or newer. Check with node -v. If you don't have it, get it from nodejs.org.
  • A Google account, which is all you need to create a free Firebase project.

You should be comfortable with React components, useState, and useEffect. You don't need to know anything about Firebase yet.


Step 1: Set up Firebase

This part happens in your browser, not in the code. It takes about five minutes.

1a. Create a Firebase project

  1. Go to console.firebase.google.com and sign in with your Google account.
  2. Click Create a project (or Add project).
  3. Name it something like cat-journal. The name doesn't matter.
  4. Google Analytics is optional — turn it off to keep things simple.
  5. Click Create project and wait for it to finish.

1b. Enable Firestore

Firestore is the database that will hold the journal entries.

  1. In the left sidebar, click Database & Storage → Firestore.
  2. Click Create database.
  3. Select standard edition. Pick a location near you. You can't change this later, but for a practice project it doesn't matter.
  4. Choose Start in test mode. This lets anyone read and write your database for the next 30 days, which is fine for learning and not fine for a real app. There's a better rule at the bottom of this README once you're signing in successfully.
  5. Click Create.

1c. Enable Google sign-in

  1. In the left sidebar, click Security → Authentication.
  2. Click Get started.
  3. On the Sign-in method tab, click Google in the list of additional providers.
  4. Flip the Enable switch, pick a support email from the dropdown, and click Save.

1d. Get your config keys

Your app needs to know which Firebase project to talk to.

  1. Click the gear icon at the top of the sidebar → Settings.
  2. Scroll to Your apps and click the web icon (</>).
  3. Give the app a nickname (cat-journal-web works). You do not need Firebase Hosting — leave that unchecked.
  4. Click Register app.
  5. Firebase shows you a firebaseConfig object. Keep this tab open — you're about to copy these values into your .env file.

Are these keys secret? No. The Firebase web API key is meant to be public and ships in every Firebase web app. What actually protects your data is Firestore security rules, not hiding this key. The .env file is still gitignored here because it's a good habit for the config files that do hold secrets.


Step 2: Install and run

From this project folder:

npm install

Then open .env and paste in the values from your firebaseConfig object:

VITE_FIREBASE_API_KEY=AIza...
VITE_FIREBASE_AUTH_DOMAIN=cat-journal-xxxxx.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=cat-journal-xxxxx
VITE_FIREBASE_STORAGE_BUCKET=cat-journal-xxxxx.firebasestorage.app
VITE_FIREBASE_MESSAGING_SENDER_ID=123456789012
VITE_FIREBASE_APP_ID=1:123456789012:web:abc123

No quotes, no spaces around the =. Then start the dev server:

npm run dev

Open the URL it prints (usually http://localhost:5173). You'll see the sign-in screen. The button won't do anything yet — that's TODO 1.

Vite reads .env once at startup. If you edit it, stop the server with Ctrl+C and run npm run dev again.


Step 3: The four TODOs

Do them in order. Each one builds on the last, and each is a few lines of code.

TODO 1 — Sign in with Google (src/Login.jsx)

Wire the button to Firebase's Google sign-in popup: create a GoogleAuthProvider, then call signInWithPopup(auth, provider) when the button is clicked.

Teaches: Firebase Authentication and OAuth providers. Firebase runs the entire Google login flow for you — you hand it a provider and it handles the popup, the consent screen, and the tokens.

After this: clicking the button opens a Google popup and you can sign in. The screen won't change yet, because nothing is listening. That's next.

TODO 2 — Listen for auth state changes (src/App.jsx)

Inside a useEffect, call onAuthStateChanged(auth, callback). The callback gets a user object when someone is signed in and null when they're not — put that in state. Return the unsubscribe function so React can clean up.

Teaches: auth listeners and useEffect cleanup. This listener fires on sign-in, on sign-out, and once on page load when Firebase restores your saved session — which is why refreshing the page keeps you logged in. Returning the unsubscribe function is how you avoid stacking up listeners every time the component remounts.

After this: signing in swaps the login screen for the journal, and the sign out button (already written) sends you back.

TODO 3 — Add a document to Firestore (src/EntryForm.jsx)

On submit, call addDoc(collection(db, "entries"), { title, text, userId, createdAt }).

Teaches: writing to Firestore and how documents are shaped. collection(db, "entries") points at a collection that Firestore creates on your first write. addDoc generates a random document ID. serverTimestamp() asks Google's server for the time instead of trusting the user's clock, so entries always sort correctly.

After this: submitting the form clears it, and nothing appears on screen — but go look at Firestore Database in the Firebase console and your entry is there. Reading it back is TODO 4.

TODO 4 — Read documents in real time (src/EntryList.jsx)

Inside a useEffect, build a query on "entries" ordered by createdAt descending, pass it to onSnapshot, and map snapshot.docs into state. Return the unsubscribe function.

Teaches: real-time listeners, Firestore queries, and cleanup. onSnapshot isn't a one-time fetch — it fires immediately with current data and then again every time the data changes. Note that the document ID lives on doc.id, separate from the fields in doc.data().

After this: the whole app works. Open it in two browser windows side by side, add an entry in one, and watch it appear in the other without a refresh. That's the payoff.


Project structure

src/
  firebase.js     Firebase config and initialization — done for you, exports db and auth
  main.jsx        React entry point
  App.jsx         Auth state and layout            (TODO 2)
  Login.jsx       Google sign-in button            (TODO 1)
  EntryForm.jsx   Form to add an entry             (TODO 3)
  EntryList.jsx   Real-time list of entries        (TODO 4)
  App.css         Styling
.env              Your Firebase config values — you fill this in

When something breaks

"Missing Firebase config" in the console — .env is empty or the dev server was started before you filled it in. Fill it in, restart the server.

auth/unauthorized-domain — Firebase console → Authentication → Settings → Authorized domains. localhost should be there by default; add it if it isn't.

auth/operation-not-allowed — Google sign-in isn't enabled. Go back to step 1c.

The popup opens and closes instantly — usually a browser popup blocker. Allow popups for localhost.

"Missing or insufficient permissions" — your Firestore rules are blocking the write. Either your test-mode 30 days expired, or the rules below are in place and you aren't signed in.

Entries save but never show up — TODO 4 isn't done, or the query is ordering by a field name that doesn't match what TODO 3 writes. Both must say createdAt.


Better security rules (optional, after everything works)

Test mode expires after 30 days and lets anyone on the internet read and write your database. Once sign-in works, replace your rules with these — Firebase console → Firestore Database → Rules:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /entries/{entryId} {
      // Anyone signed in can read entries.
      allow read: if request.auth != null;
      // You can only create entries stamped with your own user ID.
      allow create: if request.auth != null
                    && request.resource.data.userId == request.auth.uid;
      // Only the author can change or delete an entry.
      allow update, delete: if request.auth != null
                            && resource.data.userId == request.auth.uid;
    }
  }
}

This is why TODO 3 stores userId on every entry — it's what the rules check against.


Where to go next

  • Only show the signed-in user's own entries: add where("userId", "==", user.uid) to the query in EntryList.jsx. Firestore will ask you to build an index — the error in the console includes a link that creates it for you.
  • Add a delete button using deleteDoc(doc(db, "entries", entry.id)).
  • Add a photo of the cat using Firebase Storage.

About

A beginner-friendly React app that teaches Firebase fundamentals. Students build a Cat Journal using Firestore for data and Firebase Authentication for Google login.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages